Ensure that table column widths are recomputed when columns change#690
Ensure that table column widths are recomputed when columns change#690bantic merged 7 commits intoAddepar:masterfrom bantic:rondale-sc/testing-column-removal
Conversation
This fixes an issue where removing a column can leave a blank space in the
table because it doesn't recompute column widths. The table's `ResizeSensor`
is primarily responsible for noticing resizes and updating widths, but when a
column is removed, although the inner `table` element width changes, the
container `.ember-table` element does not change its width, and thus the
`ResizeSensor` never notices, and the column widths are not recomputed.
This modifies the `ember-thead` observer to have it call `fillupHandler` when
column count changes.
It also changes the observer in `ember-thead` to watch `columns.[]` instead
of just `columns`, because in the latter case, the `fillupHandler` will not
be called if a column was removed via mutation (e.g. `popObject`).
Adds tests for removal via both ways (mutation and `this.set('columns',
newColumns)`). Also adds tests that adding columns also causes column widths
to be computed.
Co-authored-by: Jonathan Jackson
|
|
||
| this.addObserver('sorts', this._updateColumnTree); | ||
| this.addObserver('columns', this._updateColumnTree); | ||
| this.addObserver('columns.[]', this._onColumnsChange); |
There was a problem hiding this comment.
Without this, modification via mutation will not be noticed and _onColumnsChange is not called.
There was a problem hiding this comment.
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.
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.
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 like popObject seems to me that it would violate the implicit contract that ember-table makes.
|
|
||
| _onColumnsChange() { | ||
| this._updateColumnTree(); | ||
| scheduleOnce('actions', this, this.fillupHandler); |
There was a problem hiding this comment.
Without the schedule, modification via mutation will not pass tests because when fillupHandler calls ensureWidthConstraint, the root's subcolumn nodes have not been updated (i.e., it doesn't yet see that a column was removed)
There was a problem hiding this comment.
@bantic something in the logic of _updateColumnTree is scheduling, or causing a schedule, into actions then? Did you hunt down what this is, and can you point me at it? I looked at _updateColumnTree and don't see anything scheduling.
There was a problem hiding this comment.
I thought this was just observer related timing problems. Am I wrong @mixonic?
There was a problem hiding this comment.
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 scheduleOnce fixes is that the ensureWidthConstraint that is called (by fillupHandler) looks at a different property to get the number of columns: the column-tree's root.subcolumnNodes. That is the property that isn't in sync with columns until/unless we use the schedule.
| }, | ||
|
|
||
| _onColumnsChange() { | ||
| if (this.get('columns.length') === 0) { |
There was a problem hiding this comment.
When would a table have 0 columns?
There was a problem hiding this comment.
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.
In our app usage, sometimes the data provided to an {{ember-table}} is empty for a little while, for instance when the source data for a table is refreshed. Without this check, ET2 will throw an exception in those cases where there are (temporarily) 0 columns
There was a problem hiding this comment.
Reviewed in-person with @mixonic . We hypothesized that the issues we see in our app may be due to interim state, where a columns property is set to [] for a moment (before later on being set to the non-empty server-provided data) and the observers in ember-thead fire eagerly. If that were the case, the _onColumnsChange hook here might be firing multiple times before the columns data has "settled" to its final state in its runloop.
I tried using a counter variable in this method that increments on each call and decrements in the scheduled callback, only doing work when it decrements back to 0, but that didn't solve the issues. It may be a mistake in our app that causes this, but we certainly run into a state where columns.length is 0 when we call fillupHandler, which causes ember-table to throw (see below).
After a bit of research, it looks like the ColumnTreeNode code (in column-tree) makes the incorrect assumption that any column that isLeaf cannot be the root, and any non-isLeaf node must have subcolumnNodes under it. When we have 0 columns, though, we end up with only a single ColumnTreeNode that is both the root and isLeaf. The ensureWidthConstraint method there runs into trouble because its assumptions about leaf vs non-leaf nodes breaks down in this case. This seems like an issue that should be fixed, but the fix can be separated from this particular PR. This PR improves the situation for adding/removing columns and doesn't fix the issue with 0 columns (but doesn't make it any worse, either).
|
|
||
| _onColumnsChange() { | ||
| this._updateColumnTree(); | ||
| scheduleOnce('actions', this, this.fillupHandler); |
There was a problem hiding this comment.
@bantic something in the logic of _updateColumnTree is scheduling, or causing a schedule, into actions then? Did you hunt down what this is, and can you point me at it? I looked at _updateColumnTree and don't see anything scheduling.
|
|
||
| this.addObserver('sorts', this._updateColumnTree); | ||
| this.addObserver('columns', this._updateColumnTree); | ||
| this.addObserver('columns.[]', this._onColumnsChange); |
There was a problem hiding this comment.
Is it a viable alternative to require the invoking code to only provide new arrays for columns? More of an immutable pattern?
|
|
||
| this.addObserver('sorts', this._updateColumnTree); | ||
| this.addObserver('columns', this._updateColumnTree); | ||
| this.addObserver('columns.[]', this._onColumnsChange); |
There was a problem hiding this comment.
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.
| }, | ||
|
|
||
| _onColumnsChange() { | ||
| if (this.get('columns.length') === 0) { |
There was a problem hiding this comment.
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.
|
|
||
| _onColumnsChange() { | ||
| this._updateColumnTree(); | ||
| scheduleOnce('actions', this, this.fillupHandler); |
There was a problem hiding this comment.
I thought this was just observer related timing problems. Am I wrong @mixonic?
This should make it so that the tests pass on Ember 1.12 and 1.13. Use a special `requestAnimationFrame` waiter that waits 2 RAF turns to ensure that the table's columns are done being laid out when the test starts measuring their dimensions.
This fixes an issue where removing a column can leave a blank space in the
table because it doesn't recompute column widths. The table's
ResizeSensoris primarily responsible for noticing resizes and updating widths, but when a
column is removed, although the inner
tableelement width changes, thecontainer
.ember-tableelement does not change its width, and thus theResizeSensornever notices, and the column widths are not recomputed.This modifies the
ember-theadobserver to have it callfillupHandlerwhencolumn count changes.
It also changes the observer in
ember-theadto watchcolumns.[]insteadof just
columns, because in the latter case, thefillupHandlerwill notbe called if a column was removed via mutation (e.g.
popObject).Adds tests for removal via both ways (mutation and
this.set('columns', newColumns)). Also adds tests that adding columns also causes column widthsto be computed.
Co-authored-by: Jonathan Jackson