Skip to content

Commit afff8b2

Browse files
authored
refactor: only re-render affected rows on grid details opened change (#11398)
1 parent 8a1eec4 commit afff8b2

File tree

3 files changed

+105
-57
lines changed

3 files changed

+105
-57
lines changed

packages/grid/src/vaadin-grid-data-provider-mixin.js

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -397,22 +397,6 @@ export const DataProviderMixin = (superClass) =>
397397
return this.getItemId(item1) === this.getItemId(item2);
398398
}
399399

400-
/**
401-
* @param {!GridItem} item
402-
* @param {!Array<!GridItem>} array
403-
* @return {number}
404-
* @protected
405-
*/
406-
_getItemIndexInArray(item, array) {
407-
let result = -1;
408-
array.forEach((i, idx) => {
409-
if (this._itemsEqual(i, item)) {
410-
result = idx;
411-
}
412-
});
413-
return result;
414-
}
415-
416400
/**
417401
* Scroll to a specific row index in the virtual list. Note that the row index is
418402
* not always the same for any particular item. For example, sorting or filtering

packages/grid/src/vaadin-grid-row-details-mixin.js

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Copyright (c) 2016 - 2026 Vaadin Ltd.
44
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
55
*/
6-
import { iterateChildren, updatePart } from './vaadin-grid-helpers.js';
6+
import { updatePart } from './vaadin-grid-helpers.js';
77

88
/**
99
* @polymerMixin
@@ -50,6 +50,15 @@ export const RowDetailsMixin = (superClass) =>
5050
_detailsCells: {
5151
type: Array,
5252
},
53+
54+
/**
55+
* Set of opened details item ids
56+
* @private
57+
*/
58+
__detailsOpenedKeys: {
59+
type: Object,
60+
computed: '__computeDetailsOpenedKeys(itemIdPath, detailsOpenedItems)',
61+
},
5362
};
5463
}
5564

@@ -79,26 +88,24 @@ export const RowDetailsMixin = (superClass) =>
7988

8089
if (this._columnTree) {
8190
// Only update the rows if the column tree has already been initialized
82-
iterateChildren(this.$.items, (row) => {
91+
this._getRenderedRows().forEach((row) => {
8392
if (!row.querySelector('[part~=details-cell]')) {
8493
this.__initRow(row, this._columnTree[this._columnTree.length - 1]);
8594
this.__updateRow(row);
95+
return;
96+
}
97+
98+
if (row.hasAttribute('details-opened')) {
99+
this.__updateRow(row);
86100
}
87101
});
88102
}
89103
}
90104

91105
/** @private */
92106
_detailsOpenedItemsChanged(_detailsOpenedItems, rowDetailsRenderer) {
93-
iterateChildren(this.$.items, (row) => {
94-
// Re-renders the row to possibly close the previously opened details.
95-
if (row.hasAttribute('details-opened')) {
96-
this.__updateRow(row);
97-
return;
98-
}
99-
100-
// Re-renders the row to open the details when a row details renderer is provided.
101-
if (rowDetailsRenderer && this._isDetailsOpened(row._item)) {
107+
this._getRenderedRows().forEach((row) => {
108+
if (row.hasAttribute('details-opened') !== !!(rowDetailsRenderer && this._isDetailsOpened(row._item))) {
102109
this.__updateRow(row);
103110
}
104111
});
@@ -165,7 +172,7 @@ export const RowDetailsMixin = (superClass) =>
165172

166173
/** @protected */
167174
_updateDetailsCellHeights() {
168-
iterateChildren(this.$.items, (row) => {
175+
this._getRenderedRows().forEach((row) => {
169176
this._updateDetailsCellHeight(row);
170177
});
171178
}
@@ -176,7 +183,17 @@ export const RowDetailsMixin = (superClass) =>
176183
* @protected
177184
*/
178185
_isDetailsOpened(item) {
179-
return this.detailsOpenedItems && this._getItemIndexInArray(item, this.detailsOpenedItems) !== -1;
186+
return this.__detailsOpenedKeys && this.__detailsOpenedKeys.has(this.getItemId(item));
187+
}
188+
189+
/** @private */
190+
__computeDetailsOpenedKeys(_itemIdPath, detailsOpenedItems) {
191+
const items = detailsOpenedItems || [];
192+
const keys = new Set();
193+
items.forEach((item) => {
194+
keys.add(this.getItemId(item));
195+
});
196+
return keys;
180197
}
181198

182199
/**

packages/grid/test/row-details.test.js

Lines changed: 75 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -172,34 +172,6 @@ describe('row details', () => {
172172
grid.closeItemDetails({ value: 'foo0' });
173173
expect(cells[1].hidden).to.be.true;
174174
});
175-
176-
it('should invoke the body renderer when opening details', () => {
177-
grid.renderer = sinon.spy();
178-
179-
openRowDetails(0);
180-
181-
grid.renderer.args.forEach(([_root, _owner, model], index) => {
182-
if (index === 0) {
183-
expect(model.detailsOpened).to.be.true;
184-
} else {
185-
expect(model.detailsOpened).to.be.false;
186-
}
187-
});
188-
});
189-
190-
it('should invoke the body renderer when closing details', () => {
191-
grid.renderer = sinon.spy();
192-
193-
openRowDetails(0);
194-
195-
grid.renderer.resetHistory();
196-
197-
closeRowDetails(0);
198-
199-
grid.renderer.args.forEach(([_root, _owner, model]) => {
200-
expect(model.detailsOpened).to.be.false;
201-
});
202-
});
203175
});
204176

205177
describe('repeat', () => {
@@ -322,6 +294,81 @@ describe('row details', () => {
322294
});
323295
});
324296

297+
describe('selective row update', () => {
298+
let renderer;
299+
300+
beforeEach(async () => {
301+
grid = fixtureSync(`
302+
<vaadin-grid style="width: 50px; height: 400px" size="100">
303+
<vaadin-grid-column></vaadin-grid-column>
304+
</vaadin-grid>
305+
`);
306+
grid.rowDetailsRenderer = simpleDetailsRenderer;
307+
renderer = sinon.spy(indexRenderer);
308+
grid.querySelector('vaadin-grid-column').renderer = renderer;
309+
310+
grid.dataProvider = infiniteDataProvider;
311+
flushGrid(grid);
312+
bodyRows = getRows(grid.$.items);
313+
await nextFrame();
314+
315+
renderer.resetHistory();
316+
});
317+
318+
it('should only invoke the body renderer for the opened row', () => {
319+
openRowDetails(1);
320+
321+
expect(renderer).to.be.calledOnce;
322+
expect(renderer.firstCall.args[2].index).to.equal(1);
323+
expect(renderer.firstCall.args[2].detailsOpened).to.be.true;
324+
});
325+
326+
it('should only invoke the body renderer for the closed row', () => {
327+
openRowDetails(1);
328+
renderer.resetHistory();
329+
330+
closeRowDetails(1);
331+
332+
expect(renderer).to.be.calledOnce;
333+
expect(renderer.firstCall.args[2].index).to.equal(1);
334+
expect(renderer.firstCall.args[2].detailsOpened).to.be.false;
335+
});
336+
337+
it('should only invoke the body renderer for the newly opened row', () => {
338+
openRowDetails(0);
339+
renderer.resetHistory();
340+
341+
openRowDetails(2);
342+
343+
expect(renderer).to.be.calledOnce;
344+
expect(renderer.firstCall.args[2].index).to.equal(2);
345+
expect(renderer.firstCall.args[2].detailsOpened).to.be.true;
346+
});
347+
348+
it('should only invoke the body renderer for affected rows when replacing detailsOpenedItems', () => {
349+
openRowDetails(0);
350+
renderer.resetHistory();
351+
352+
grid.detailsOpenedItems = [grid._dataProviderController.rootCache.items[2]];
353+
flushGrid(grid);
354+
355+
expect(renderer).to.be.calledTwice;
356+
const renderedIndexes = renderer.getCalls().map((call) => call.args[2].index);
357+
expect(renderedIndexes).to.include(0);
358+
expect(renderedIndexes).to.include(2);
359+
});
360+
361+
it('should not invoke the body renderer when detailsOpenedItems is set to the same value', () => {
362+
openRowDetails(1);
363+
renderer.resetHistory();
364+
365+
grid.detailsOpenedItems = [...grid.detailsOpenedItems];
366+
flushGrid(grid);
367+
368+
expect(renderer).to.not.be.called;
369+
});
370+
});
371+
325372
describe('lazily set details renderer', () => {
326373
beforeEach(async () => {
327374
grid = fixtureSync(`

0 commit comments

Comments
 (0)