-
Notifications
You must be signed in to change notification settings - Fork 355
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ensure that table column widths are recomputed when columns change #690
Changes from 2 commits
bbe22b4
a67a2b1
4c9143f
0fc27a4
280efd5
c8814b3
8575206
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ import { notEmpty, or } from '@ember/object/computed'; | |
|
||
import { closest } from '../../-private/utils/element'; | ||
import { sortMultiple, compareValues } from '../../-private/utils/sort'; | ||
import { scheduleOnce } from '@ember/runloop'; | ||
|
||
import ColumnTree, { RESIZE_MODE, FILL_MODE, WIDTH_CONSTRAINT } from '../../-private/column-tree'; | ||
|
||
|
@@ -195,7 +196,7 @@ export default Component.extend({ | |
this.addObserver('reorderFunction', this._updateApi); | ||
|
||
this.addObserver('sorts', this._updateColumnTree); | ||
this.addObserver('columns', this._updateColumnTree); | ||
this.addObserver('columns.[]', this._onColumnsChange); | ||
this.addObserver('fillMode', this._updateColumnTree); | ||
this.addObserver('resizeMode', this._updateColumnTree); | ||
this.addObserver('widthConstraint', this._updateColumnTree); | ||
|
@@ -225,17 +226,22 @@ export default Component.extend({ | |
this.columnTree.set('enableReorder', this.get('enableReorder')); | ||
}, | ||
|
||
_onColumnsChange() { | ||
if (this.get('columns.length') === 0) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When would a table have 0 columns? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm relatively certain this was to address issues with the testing scenario whcih has a button that removes a column, if you continue to remove columns after you reach 0 you get a unrelated error. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In our app usage, sometimes the data provided to an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reviewed in-person with @mixonic . We hypothesized that the issues we see in our app may be due to interim state, where a After a bit of research, it looks like the |
||
return; | ||
} | ||
this._updateColumnTree(); | ||
scheduleOnce('actions', this, this.fillupHandler); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Without the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bantic something in the logic of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought this was just observer related timing problems. Am I wrong @mixonic? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I haven't looked into the downstream source of the problem. The code uses what seems like an intermediate "caching" layer and I am not clear on how it works — the problem that this |
||
}, | ||
|
||
didInsertElement() { | ||
this._super(...arguments); | ||
|
||
this._container = closest(this.element, '.ember-table'); | ||
|
||
this.columnTree.registerContainer(this._container); | ||
|
||
this._tableResizeSensor = new ResizeSensor( | ||
this._container, | ||
bind(this.fillupHandler.bind(this)) | ||
); | ||
this._tableResizeSensor = new ResizeSensor(this._container, bind(this, this.fillupHandler)); | ||
}, | ||
|
||
willDestroyElement() { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
import { module, test } from 'qunit'; | ||
import { setupRenderingTest } from 'ember-qunit'; | ||
import { click, render } from '@ember/test-helpers'; | ||
import hbs from 'htmlbars-inline-precompile'; | ||
import TablePage from 'ember-table/test-support/pages/ember-table'; | ||
import { A } from '@ember/array'; | ||
|
||
function tableData() { | ||
return { | ||
rows: [ | ||
{ A: 'A', B: 'B', C: 'C', D: 'D', E: 'E' }, | ||
{ A: 'A', B: 'B', C: 'C', D: 'D', E: 'E' }, | ||
{ A: 'A', B: 'B', C: 'C', D: 'D', E: 'E' }, | ||
], | ||
columns: [ | ||
{ name: 'A', valuePath: 'A', width: 180 }, | ||
{ name: 'B', valuePath: 'B', width: 180 }, | ||
{ name: 'C', valuePath: 'C', width: 180 }, | ||
{ name: 'D', valuePath: 'D', width: 180 }, | ||
], | ||
newColumn: { name: 'E', valuePath: 'E', width: 180 }, | ||
}; | ||
} | ||
|
||
function sumHeaderWidths(table) { | ||
return table.headers.map(h => h.width).reduce((sum, w) => sum + w, 0); | ||
} | ||
|
||
async function renderTable() { | ||
await render(hbs` | ||
<button id="add-column" {{action 'addColumn'}}>Add Column</button> | ||
<button id="remove-column" {{action 'removeColumn'}}>Remove Column</button> | ||
{{#ember-table data-test-ember-table=true as |t|}} | ||
{{#t.head | ||
widthConstraint='eq-container' | ||
columns=columns as |h|}} | ||
{{#h.row as |r|}} | ||
{{r.cell}} | ||
{{/h.row}} | ||
{{/t.head}} | ||
|
||
{{#t.body rows=rows as |b|}} | ||
{{#b.row as |r|}} | ||
{{#r.cell as |cellValue|}} | ||
{{cellValue}} | ||
{{/r.cell}} | ||
{{/b.row}} | ||
{{/t.body}} | ||
{{/ember-table}} | ||
`); | ||
} | ||
|
||
async function testColumnRemovals(assert, table) { | ||
let originalWidth = table.width; | ||
let originalContainerWidth = table.containerWidth; | ||
|
||
let currentColumnCount = table.headers.length; | ||
assert.equal(currentColumnCount, 4, 'precond - 4 columns'); | ||
assert.equal(sumHeaderWidths(table), originalWidth, 'precond - headers sum to table width'); | ||
assert.equal( | ||
originalWidth, | ||
originalContainerWidth, | ||
'precond - table is as wide as its container' | ||
); | ||
|
||
while (currentColumnCount > 1) { | ||
await click('button#remove-column'); | ||
assert.equal( | ||
table.headers.length, | ||
currentColumnCount - 1, | ||
`column count changes from ${currentColumnCount} -> ${currentColumnCount - 1}` | ||
); | ||
currentColumnCount -= 1; | ||
|
||
assert.equal( | ||
table.width, | ||
originalWidth, | ||
`table width is same after removal of column #${currentColumnCount}.` | ||
); | ||
assert.equal( | ||
table.containerWidth, | ||
originalContainerWidth, | ||
`new table container is same size as original container after removal of column #${currentColumnCount}.` | ||
); | ||
assert.equal( | ||
sumHeaderWidths(table), | ||
originalWidth, | ||
`headers sum to table width after removal of column #${currentColumnCount}` | ||
); | ||
assert.equal( | ||
table.width, | ||
table.containerWidth, | ||
`new table width is as wide as its container after removal of column #${currentColumnCount}.` | ||
); | ||
} | ||
} | ||
|
||
async function testColumnAddition(assert, table) { | ||
let originalWidth = table.width; | ||
let originalContainerWidth = table.containerWidth; | ||
|
||
let currentColumnCount = table.headers.length; | ||
assert.equal(currentColumnCount, 4, 'precond - 4 columns'); | ||
assert.equal(sumHeaderWidths(table), originalWidth, 'precond - headers sum to table width'); | ||
assert.equal( | ||
originalWidth, | ||
originalContainerWidth, | ||
'precond - table is as wide as its container' | ||
); | ||
|
||
await click('#add-column'); | ||
assert.equal(table.headers.length, 5, 'column is added'); | ||
assert.equal(sumHeaderWidths(table), originalWidth, 'headers sum to table width after adding'); | ||
assert.equal(table.width, originalWidth, 'table width is unchanged'); | ||
assert.equal(table.containerWidth, originalContainerWidth, 'table container width is unchanged'); | ||
} | ||
|
||
module('ember-thead', function(hooks) { | ||
setupRenderingTest(hooks); | ||
|
||
test('table resizes when columns are removed', async function(assert) { | ||
let data = tableData(); | ||
this.set('rows', data.rows); | ||
this.set('columns', data.columns); | ||
this.set('removeColumn', function() { | ||
this.set('columns', this.get('columns').slice(0, -1)); | ||
}); | ||
|
||
await renderTable(); | ||
await testColumnRemovals(assert, new TablePage()); | ||
}); | ||
|
||
test('table resizes when columns are removed via mutation', async function(assert) { | ||
let data = tableData(); | ||
this.set('rows', data.rows); | ||
this.set('columns', A(data.columns)); | ||
this.set('removeColumn', function() { | ||
this.get('columns').popObject(); | ||
}); | ||
|
||
await renderTable(); | ||
await testColumnRemovals(assert, new TablePage()); | ||
}); | ||
|
||
test('table resizes when columns are added', async function(assert) { | ||
let data = tableData(); | ||
this.set('rows', data.rows); | ||
this.set('columns', data.columns); | ||
this.set('addColumn', function() { | ||
this.set('columns', [...data.columns, data.newColumn]); | ||
}); | ||
|
||
await renderTable(); | ||
await testColumnAddition(assert, new TablePage()); | ||
}); | ||
|
||
test('table resizes when columns are added via mutation', async function(assert) { | ||
let data = tableData(); | ||
this.set('rows', data.rows); | ||
this.set('columns', A(data.columns)); | ||
this.set('addColumn', function() { | ||
this.get('columns').pushObject(data.newColumn); | ||
}); | ||
|
||
await renderTable(); | ||
await testColumnAddition(assert, new TablePage()); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without this, modification via mutation will not be noticed and
_onColumnsChange
is not called.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it a viable alternative to require the invoking code to only provide new arrays for
columns
? More of an immutable pattern?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We did discuss that @mixonic, but usage that we've seen really runs the gamut. Feels a little awkward to disallow mutation with Ember compatible mutations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be one option, yes, and that would fix the problem. It wouldn't be hard to change it in our consuming app code (I don't think). But not making ember-table's awareness of
columns
kvo-compliant with Ember-provided kvo-compliant methods likepopObject
seems to me that it would violate the implicit contract that ember-table makes.