Skip to content

Commit 5b579d7

Browse files
yuriy-fixtulioagsissbruecker
authored
fix: update filtering and sorting when grid and columns are attached / detached (#2122)
Fixes: #1969 Warranty: Fixes exception related to sorting when recreating columns Co-authored-by: Tulio Garcia <[email protected]> Co-authored-by: Sascha Ißbrücker <[email protected]>
1 parent 8b74a1f commit 5b579d7

7 files changed

+231
-30
lines changed

src/vaadin-grid-dynamic-columns-mixin.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,14 @@ export const DynamicColumnsMixin = (superClass) =>
8787
if (rowDetailsTemplate && this._rowDetailsTemplate !== rowDetailsTemplate) {
8888
this._rowDetailsTemplate = rowDetailsTemplate;
8989
}
90-
91-
if (
92-
info.addedNodes.filter(this._isColumnElement).length > 0 ||
93-
info.removedNodes.filter(this._isColumnElement).length > 0
94-
) {
90+
const hasColumnElements = (nodeCollection) => nodeCollection.filter(this._isColumnElement).length > 0;
91+
if (hasColumnElements(info.addedNodes) || hasColumnElements(info.removedNodes)) {
92+
const allRemovedCells = info.removedNodes.flatMap((c) => c._allCells);
93+
const filterNotConnected = (element) =>
94+
allRemovedCells.filter((cell) => cell._content.contains(element)).length;
95+
96+
this.__removeSorters(this._sorters.filter(filterNotConnected));
97+
this.__removeFilters(this._filters.filter(filterNotConnected));
9598
this._updateColumnTree();
9699
}
97100

src/vaadin-grid-filter-mixin.js

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,34 @@ export const FilterMixin = (superClass) =>
2929

3030
/** @private */
3131
_filterChanged(e) {
32-
if (this._filters.indexOf(e.target) === -1) {
33-
this._filters.push(e.target);
32+
e.stopPropagation();
33+
34+
this.__addFilter(e.target);
35+
this.__applyFilters();
36+
}
37+
38+
/** @private */
39+
__removeFilters(filtersToRemove) {
40+
if (filtersToRemove.length == 0) {
41+
return;
3442
}
3543

36-
e.stopPropagation();
44+
this._filters = this._filters.filter((filter) => filtersToRemove.indexOf(filter) < 0);
45+
this.__applyFilters();
46+
}
3747

38-
if (this.dataProvider) {
48+
/** @private */
49+
__addFilter(filter) {
50+
const filterIndex = this._filters.indexOf(filter);
51+
52+
if (filterIndex === -1) {
53+
this._filters.push(filter);
54+
}
55+
}
56+
57+
/** @private */
58+
__applyFilters() {
59+
if (this.dataProvider && this.isAttached) {
3960
this.clearCache();
4061
}
4162
}

src/vaadin-grid-sort-mixin.js

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,31 +51,61 @@ export const SortMixin = (superClass) =>
5151
/** @private */
5252
_onSorterChanged(e) {
5353
const sorter = e.target;
54+
e.stopPropagation();
55+
this.__updateSorter(sorter);
56+
this.__applySorters();
57+
}
58+
59+
/** @private */
60+
__removeSorters(sortersToRemove) {
61+
if (sortersToRemove.length == 0) {
62+
return;
63+
}
64+
65+
this._sorters = this._sorters.filter((sorter) => sortersToRemove.indexOf(sorter) < 0);
66+
if (this.multiSort) {
67+
this.__updateSortOrders();
68+
}
69+
this.__applySorters();
70+
}
71+
72+
/** @private */
73+
__updateSortOrders() {
74+
this._sorters.forEach((sorter, index) => (sorter._order = this._sorters.length > 1 ? index : null), this);
75+
}
76+
77+
/** @private */
78+
__updateSorter(sorter) {
79+
if (!sorter.direction && this._sorters.indexOf(sorter) === -1) {
80+
return;
81+
}
5482

55-
this._removeArrayItem(this._sorters, sorter);
5683
sorter._order = null;
5784

5885
if (this.multiSort) {
86+
this._removeArrayItem(this._sorters, sorter);
5987
if (sorter.direction) {
6088
this._sorters.unshift(sorter);
6189
}
62-
63-
this._sorters.forEach((sorter, index) => (sorter._order = this._sorters.length > 1 ? index : null), this);
90+
this.__updateSortOrders();
6491
} else {
6592
if (sorter.direction) {
66-
this._sorters.forEach((sorter) => {
93+
const otherSorters = this._sorters.filter((s) => s != sorter);
94+
this._sorters = [sorter];
95+
otherSorters.forEach((sorter) => {
6796
sorter._order = null;
6897
sorter.direction = null;
6998
});
70-
this._sorters = [sorter];
7199
}
72100
}
101+
}
73102

74-
e.stopPropagation();
75-
103+
/** @private */
104+
__applySorters() {
76105
if (
77106
this.dataProvider &&
78-
// No need to clear cache if sorters didn't change
107+
// No need to clear cache if sorters didn't change and grid is attached
108+
this.isAttached &&
79109
JSON.stringify(this._previousSorters) !== JSON.stringify(this._mapSorters())
80110
) {
81111
this.clearCache();

src/vaadin-grid-sorter.js

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,13 @@ class GridSorterElement extends ThemableMixin(DirMixin(PolymerElement)) {
145145
/** @private */
146146
_isConnected: {
147147
type: Boolean,
148-
value: false
148+
observer: '__isConnectedChanged'
149149
}
150150
};
151151
}
152152

153153
static get observers() {
154-
return ['_pathOrDirectionChanged(path, direction, _isConnected)'];
154+
return ['_pathOrDirectionChanged(path, direction)'];
155155
}
156156

157157
/** @protected */
@@ -173,14 +173,26 @@ class GridSorterElement extends ThemableMixin(DirMixin(PolymerElement)) {
173173
}
174174

175175
/** @private */
176-
_pathOrDirectionChanged(path, direction, isConnected) {
177-
if (path === undefined || direction === undefined || isConnected === undefined) {
176+
_pathOrDirectionChanged() {
177+
this.__dispatchSorterChangedEvenIfPossible();
178+
}
179+
180+
/** @private */
181+
__isConnectedChanged(newValue, oldValue) {
182+
if (oldValue === false) {
178183
return;
179184
}
180185

181-
if (isConnected) {
182-
this.dispatchEvent(new CustomEvent('sorter-changed', { bubbles: true, composed: true }));
186+
this.__dispatchSorterChangedEvenIfPossible();
187+
}
188+
189+
/** @private */
190+
__dispatchSorterChangedEvenIfPossible() {
191+
if (this.path === undefined || this.direction === undefined || !this._isConnected) {
192+
return;
183193
}
194+
195+
this.dispatchEvent(new CustomEvent('sorter-changed', { bubbles: true, composed: true }));
184196
}
185197

186198
/** @private */

test/filtering.test.js

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@ import sinon from 'sinon';
33
import { fixtureSync } from '@open-wc/testing-helpers';
44
import { PolymerElement, html } from '@polymer/polymer/polymer-element.js';
55
import { flush } from '@polymer/polymer/lib/utils/flush.js';
6-
import { flushGrid, getBodyCellContent, getHeaderCellContent, listenOnce, scrollToEnd } from './helpers.js';
6+
import {
7+
flushGrid,
8+
getBodyCellContent,
9+
getHeaderCellContent,
10+
listenOnce,
11+
scrollToEnd,
12+
getVisibleItems
13+
} from './helpers.js';
714
import '../vaadin-grid.js';
815
import '../vaadin-grid-filter.js';
916
import '../vaadin-grid-filter-column.js';
@@ -67,7 +74,7 @@ describe('filter', () => {
6774
});
6875

6976
describe('filtering', () => {
70-
let grid;
77+
let grid, filter;
7178

7279
beforeEach(() => {
7380
grid = fixtureSync(`
@@ -95,6 +102,7 @@ describe('filtering', () => {
95102
if (grid._observer.flush) {
96103
grid._observer.flush();
97104
}
105+
filter = grid._filters[0];
98106
});
99107

100108
it('should have filters', () => {
@@ -107,6 +115,21 @@ describe('filtering', () => {
107115
grid._filters.forEach((filter) => expect(filter.$.filter.clientHeight).to.be.greaterThan(0));
108116
});
109117

118+
it('should not keep references to filters when column is removed', () => {
119+
grid.removeChild(grid.firstElementChild);
120+
flushGrid(grid);
121+
expect(grid._filters).to.not.contain(filter);
122+
});
123+
124+
it('should keep references to filters for columns that are not removed', () => {
125+
expect(grid._filters.length).to.eql(2);
126+
expect(grid._filters[1].path).to.eql('last');
127+
grid.removeChild(grid.firstElementChild.nextElementSibling);
128+
flushGrid(grid);
129+
expect(grid._filters.length).to.eql(1);
130+
expect(grid._filters[0].path).to.eql('first');
131+
});
132+
110133
it('should pass filters to dataProvider', (done) => {
111134
grid.size = 10;
112135

@@ -190,7 +213,7 @@ describe('filtering', () => {
190213
});
191214

192215
describe('array data provider', () => {
193-
let grid;
216+
let grid, filterFirst, filterSecond;
194217

195218
beforeEach(() => {
196219
grid = fixtureSync(`
@@ -216,8 +239,11 @@ describe('array data provider', () => {
216239
flushGrid(grid);
217240

218241
flushFilters(grid);
219-
grid._filters[0].value = '';
220-
grid._filters[1].value = '';
242+
filterFirst = grid._filters[0];
243+
filterSecond = grid._filters[1];
244+
245+
filterFirst.value = '';
246+
filterSecond.value = '';
221247
flushFilters(grid);
222248
grid.items = [
223249
{
@@ -252,6 +278,32 @@ describe('array data provider', () => {
252278
expect(getBodyCellContent(grid, 0, 0).innerText).to.equal('bar');
253279
});
254280

281+
it('should update filtering when column is removed', () => {
282+
filterFirst.value = 'bar';
283+
flushFilters(grid);
284+
285+
grid.removeChild(grid.firstElementChild);
286+
flushGrid(grid);
287+
288+
expect(getVisibleItems(grid).length).to.equal(3);
289+
});
290+
291+
it('should not filter items before grid is re-attached', () => {
292+
filterFirst.value = 'bar';
293+
flushFilters(grid);
294+
295+
const parentNode = grid.parentNode;
296+
parentNode.removeChild(grid);
297+
grid.removeChild(grid.firstElementChild);
298+
flushGrid(grid);
299+
300+
expect(Object.keys(grid._cache.items).length).to.equal(1);
301+
302+
parentNode.appendChild(grid);
303+
304+
expect(Object.keys(grid._cache.items).length).to.equal(3);
305+
});
306+
255307
it('should sort filtered items', () => {
256308
grid._filters[1].value = 'r';
257309
grid.querySelector('vaadin-grid-sorter').direction = 'asc';

test/helpers.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export const isVisible = (el) => {
113113
);
114114
};
115115

116-
const getVisibleItems = (grid) => {
116+
export const getVisibleItems = (grid) => {
117117
flushGrid(grid);
118118
const rows = grid.$.items.children;
119119
const visibleRows = [];

test/sorting.test.js

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,89 @@ describe('sorting', () => {
7676
});
7777
});
7878

79+
describe('DOM operations', () => {
80+
let grid, sorterFirst, sorterSecond, sorterThird, columnFirst, columnThird;
81+
82+
beforeEach(async () => {
83+
grid = fixtureSync(`
84+
<vaadin-grid style="width: 400px; height: 200px;" multi-sort>
85+
<vaadin-grid-sort-column path="first" direction="desc"></vaadin-grid-sort-column>
86+
<vaadin-grid-sort-column path="second" direction="asc"></vaadin-grid-sort-column>
87+
<vaadin-grid-sort-column path="third" direction="desc"></vaadin-grid-sort-column>
88+
</vaadin-grid>
89+
`);
90+
await nextFrame();
91+
92+
// TODO: find better way to select
93+
columnFirst = grid.querySelectorAll('vaadin-grid-sort-column')[0];
94+
columnThird = grid.querySelectorAll('vaadin-grid-sort-column')[2];
95+
96+
sorterFirst = getHeaderCellContent(grid, 0, 0).querySelector('vaadin-grid-sorter');
97+
sorterSecond = getHeaderCellContent(grid, 0, 1).querySelector('vaadin-grid-sorter');
98+
sorterThird = getHeaderCellContent(grid, 0, 2).querySelector('vaadin-grid-sorter');
99+
100+
grid.items = [
101+
{ first: '1', second: '2', third: '3' },
102+
{ first: '2', second: '3', third: '1' },
103+
{ first: '3', second: '1', third: '2' }
104+
];
105+
106+
flushGrid(grid);
107+
});
108+
109+
it('should preserve sort order for sorters when grid is re-attached', () => {
110+
click(sorterSecond);
111+
const parentNode = grid.parentNode;
112+
parentNode.removeChild(grid);
113+
parentNode.appendChild(grid);
114+
115+
expect(sorterFirst._order).to.equal(2);
116+
expect(sorterSecond._order).to.equal(0);
117+
expect(sorterThird._order).to.equal(1);
118+
});
119+
120+
it('should not keep references to sorters when column is removed', () => {
121+
grid.removeChild(columnFirst);
122+
flushGrid(grid);
123+
expect(grid._sorters).to.not.contain(sorterFirst);
124+
});
125+
126+
it('should update sorting when column is removed', () => {
127+
grid.removeChild(columnThird);
128+
flushGrid(grid);
129+
130+
expect(getBodyCellContent(grid, 0, 0).innerText).to.equal('3');
131+
expect(getBodyCellContent(grid, 1, 0).innerText).to.equal('1');
132+
expect(getBodyCellContent(grid, 2, 0).innerText).to.equal('2');
133+
134+
expect(getBodyCellContent(grid, 0, 1).innerText).to.equal('1');
135+
expect(getBodyCellContent(grid, 1, 1).innerText).to.equal('2');
136+
expect(getBodyCellContent(grid, 2, 1).innerText).to.equal('3');
137+
});
138+
139+
it('should update sort order when column removed and grid is not attached', () => {
140+
const parentNode = grid.parentNode;
141+
parentNode.removeChild(grid);
142+
143+
grid.removeChild(columnThird);
144+
flushGrid(grid);
145+
expect(sorterFirst._order).to.equal(1);
146+
expect(sorterSecond._order).to.equal(0);
147+
});
148+
149+
it('should not sort items before grid is re-attached', () => {
150+
const parentNode = grid.parentNode;
151+
parentNode.removeChild(grid);
152+
153+
grid.removeChild(columnThird);
154+
flushGrid(grid);
155+
expect(getBodyCellContent(grid, 0, 1).innerText).to.equal('2');
156+
157+
parentNode.appendChild(grid);
158+
expect(getBodyCellContent(grid, 0, 1).innerText).to.equal('1');
159+
});
160+
});
161+
79162
describe('grid', () => {
80163
let grid, sorterFirst, sorterLast;
81164

@@ -286,7 +369,7 @@ describe('sorting', () => {
286369
grid.dataProvider.resetHistory();
287370
sorterFirst.direction = 'desc';
288371

289-
expect(grid.dataProvider.args[1][0].sortOrders.length).to.eql(1);
372+
expect(grid.dataProvider.args[0][0].sortOrders.length).to.eql(1);
290373
});
291374

292375
it('should remove order from sorters', () => {

0 commit comments

Comments
 (0)