Skip to content

Commit fd836a8

Browse files
authored
DataGrid - Refresh selectedItems on dataSource change when repaintChangesOnly=true (T1279797) (DevExpress#30812)
1 parent bdbddc3 commit fd836a8

File tree

4 files changed

+192
-16
lines changed

4 files changed

+192
-16
lines changed

packages/devextreme/js/__internal/grids/grid_core/data_controller/m_data_controller.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* eslint-disable @typescript-eslint/method-signature-style */
21
import ArrayStore from '@js/common/data/array_store';
32
import { CustomStore } from '@js/common/data/custom_store';
43
import $ from '@js/core/renderer';
@@ -609,7 +608,6 @@ export class DataController extends DataHelperMixin(modules.Controller) {
609608
this.pushed.fire(changes);
610609
}
611610

612-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
613611
public fireError(...args: any[]) {
614612
this.dataErrorOccurred.fire(errors.Error.apply(errors, args));
615613
}
@@ -1637,7 +1635,7 @@ export class DataController extends DataHelperMixin(modules.Controller) {
16371635
if (options === true) {
16381636
options = { reload: true, changesOnly: true };
16391637
} else if (!options) {
1640-
options = { lookup: true, selection: true, reload: true };
1638+
options = { reload: true, lookup: true };
16411639
}
16421640

16431641
const that = this;
@@ -1729,7 +1727,7 @@ export class DataController extends DataHelperMixin(modules.Controller) {
17291727
/**
17301728
* @extended: editing, virtual_scrolling
17311729
*/
1732-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
1730+
17331731
public reload(reload?, changesOnly?): any {
17341732
return this._dataSource?.reload(reload, changesOnly);
17351733
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import {
2+
afterEach, describe, expect, it,
3+
} from '@jest/globals';
4+
import { CustomStore } from '@js/common/data';
5+
import type { dxElementWrapper } from '@js/core/renderer';
6+
import $ from '@js/core/renderer';
7+
8+
import type { GridsEditRefreshMode, Properties as DataGridProperties } from '../../../../ui/data_grid';
9+
import DataGrid from '../../../../ui/data_grid';
10+
11+
const SELECTORS = {
12+
gridContainer: '#gridContainer',
13+
detailCell: 'dx-master-detail-cell',
14+
detailContainer: 'dx-datagrid-master-detail-container',
15+
};
16+
17+
const GRID_CONTAINER_ID = 'gridContainer';
18+
19+
const createDataGrid = async (
20+
options: DataGridProperties = {},
21+
): Promise<{ $container: dxElementWrapper; instance: DataGrid }> => new Promise((resolve) => {
22+
const $container = $('<div>')
23+
.attr('id', GRID_CONTAINER_ID)
24+
.appendTo(document.body);
25+
26+
const instance = new DataGrid($container.get(0) as HTMLDivElement, options);
27+
28+
const contentReadyHandler = (): void => {
29+
resolve({ $container, instance });
30+
instance.off('contentReady', contentReadyHandler);
31+
};
32+
33+
instance.on('contentReady', contentReadyHandler);
34+
});
35+
36+
describe('GridCore selection', () => {
37+
afterEach(() => {
38+
const $container = $(SELECTORS.gridContainer);
39+
40+
const dataGrid = ($container as any).dxDataGrid('instance') as DataGrid;
41+
42+
dataGrid.dispose();
43+
$container.remove();
44+
});
45+
46+
describe('selectionChanged handler', () => {
47+
[true, false].forEach((repaintChangesOnly) => {
48+
it(`selectRowKeys are updated after refresh if selectedItem is not in dataSource anymore with repaintChangesOnly=${repaintChangesOnly}`, async () => {
49+
const dataSource = [
50+
{ id: 1, name: 'Item 1' },
51+
{ id: 2, name: 'Item 2' },
52+
];
53+
54+
const { instance } = await createDataGrid({
55+
dataSource,
56+
columns: ['id', 'name'],
57+
keyExpr: 'id',
58+
selection: {
59+
mode: 'single',
60+
},
61+
repaintChangesOnly,
62+
});
63+
64+
await instance.selectRows([2], false);
65+
expect(instance.getSelectedRowKeys()).toEqual([2]);
66+
67+
dataSource.splice(1, 1); // Remove the item with id 2
68+
69+
await instance.refresh(repaintChangesOnly);
70+
71+
expect(instance.getSelectedRowKeys()).toEqual([]);
72+
});
73+
74+
it(`selectionChanged handler is not called after refresh if selectedItem still present in dataSource with repaintChangesOnly=${repaintChangesOnly}`, async () => {
75+
const dataSource = [
76+
{ id: 1, name: 'Item 1' },
77+
{ id: 2, name: 'Item 2' },
78+
];
79+
80+
let selectionChangedCount = 0;
81+
82+
const { instance } = await createDataGrid({
83+
dataSource,
84+
columns: ['id', 'name'],
85+
keyExpr: 'id',
86+
selection: {
87+
mode: 'single',
88+
},
89+
repaintChangesOnly,
90+
onSelectionChanged: () => {
91+
selectionChangedCount += 1;
92+
},
93+
});
94+
95+
await instance.selectRows([1], false);
96+
expect(instance.getSelectedRowKeys()).toEqual([1]);
97+
expect(selectionChangedCount).toBe(1);
98+
99+
dataSource.splice(1, 1); // Remove the item with id 2
100+
await instance.refresh(repaintChangesOnly);
101+
102+
expect(instance.getSelectedRowKeys()).toEqual([1]);
103+
expect(selectionChangedCount).toBe(1);
104+
});
105+
});
106+
});
107+
108+
describe('remote dataSource', () => {
109+
([
110+
{ refreshMode: 'full', expectedCallCount: 2 },
111+
{ refreshMode: 'reshape', expectedCallCount: 1 },
112+
{ refreshMode: 'repaint', expectedCallCount: 0 },
113+
] as { refreshMode: GridsEditRefreshMode; expectedCallCount: number }[])
114+
.forEach(({ refreshMode, expectedCallCount }) => {
115+
it(`dataSource.load is not called to load selectedRow after data save with editing.refreshMode=${refreshMode}`, async () => {
116+
let data = [
117+
{ id: 1, name: 'Item 1' },
118+
{ id: 2, name: 'Item 2' },
119+
{ id: 3, name: 'Item 3' },
120+
{ id: 4, name: 'Item 4' },
121+
];
122+
123+
const store = new CustomStore({
124+
key: 'id',
125+
load: (e) => {
126+
const skip = e.skip ?? 0;
127+
const take = e.take ?? data.length;
128+
const pageData = data.slice(skip, skip + take);
129+
return Promise.resolve({
130+
data: pageData,
131+
totalCount: data.length,
132+
});
133+
},
134+
remove(key) {
135+
data = data.filter((item) => item.id !== key);
136+
return Promise.resolve();
137+
},
138+
});
139+
140+
const { instance } = await createDataGrid({
141+
dataSource: store,
142+
editing: {
143+
mode: 'batch',
144+
refreshMode,
145+
allowDeleting: true,
146+
},
147+
remoteOperations: true,
148+
paging: {
149+
pageSize: 2,
150+
},
151+
columns: ['id', 'name'],
152+
keyExpr: 'id',
153+
selection: {
154+
mode: 'multiple',
155+
showCheckBoxesMode: 'always',
156+
},
157+
});
158+
159+
await instance.selectRows([4], false);
160+
161+
let callCount = 0;
162+
store.on('loading', () => {
163+
callCount += 1;
164+
});
165+
166+
instance.option('editing.changes', [{
167+
type: 'remove',
168+
key: 1,
169+
}]);
170+
await instance.saveEditData();
171+
172+
expect(callCount).toBe(expectedCallCount);
173+
});
174+
});
175+
});
176+
});

packages/devextreme/js/__internal/grids/grid_core/selection/m_selection.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type { DeferredObj } from '@js/core/utils/deferred';
1212
import { Deferred } from '@js/core/utils/deferred';
1313
import { extend } from '@js/core/utils/extend';
1414
import { each } from '@js/core/utils/iterator';
15-
import { isDefined } from '@js/core/utils/type';
15+
import { isDefined, isObject } from '@js/core/utils/type';
1616
import errors from '@js/ui/widget/ui.errors';
1717
import supportUtils from '@ts/core/utils/m_support';
1818
import type { ColumnHeadersView } from '@ts/grids/grid_core/column_headers/m_column_headers';
@@ -650,17 +650,19 @@ export const dataSelectionExtenderMixin = (Base: ModuleType<DataController>) =>
650650
return dataItem;
651651
}
652652

653-
public refresh(options) {
654-
const that = this;
653+
public refresh(options): any {
655654
// @ts-expect-error
656-
const d = new Deferred();
655+
const d: DeferredObj<void> = new Deferred();
656+
657+
super.refresh(options).done(() => {
658+
const skipSelectionRefresh = isObject(options) && !(options as any).selection;
657659

658-
super.refresh.apply(this, arguments as any).done(() => {
659-
if (!options || options.selection) {
660-
that._selectionController.refresh().done(d.resolve).fail(d.reject);
661-
} else {
660+
if (skipSelectionRefresh) {
662661
d.resolve();
662+
return;
663663
}
664+
665+
this._selectionController.refresh().done(d.resolve).fail(d.reject);
664666
}).fail(d.reject);
665667

666668
return d.promise();

packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/selection.tests.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,7 +1119,7 @@ QUnit.module('Selection', { beforeEach: setupSelectionModule, afterEach: teardow
11191119
assert.strictEqual(selectionChangedCount, 2, 'selection changed raised');
11201120
});
11211121

1122-
QUnit.test('Not rise selectionChanged event on refresh with changesOnly', function(assert) {
1122+
QUnit.test('Rise selectionChanged event on refresh with changesOnly', function(assert) {
11231123
let selectionChangedCount = 0;
11241124

11251125
this.applyOptions({
@@ -1137,8 +1137,8 @@ QUnit.module('Selection', { beforeEach: setupSelectionModule, afterEach: teardow
11371137
this.dataController.refresh(true);
11381138

11391139
// assert
1140-
assert.deepEqual(this.selectionController.getSelectedRowKeys(), [{ name: 'Dan', age: 16 }, { name: 'Dmitry', age: 18 }]);
1141-
assert.strictEqual(selectionChangedCount, 1, 'selection changed is not raised');
1140+
assert.deepEqual(this.selectionController.getSelectedRowKeys(), [{ name: 'Dan', age: 16 }]);
1141+
assert.strictEqual(selectionChangedCount, 2, 'selection changed is raised');
11421142
});
11431143

11441144
QUnit.test('Not rise selectionChanged event on apply filter when selectedRows count not changed', function(assert) {
@@ -2052,7 +2052,7 @@ QUnit.module('ChangeRowSelection for multiple selection. DataSource with key', {
20522052
this.selectionController.changeItemSelection(3, { shift: true });
20532053

20542054
// assert
2055-
assert.deepEqual(this.selectionController.getSelectedRowKeys(), [2, 4, 3], 'selectedRowKeys');
2055+
assert.deepEqual(this.selectionController.getSelectedRowKeys(), [2, 3, 4], 'selectedRowKeys');
20562056
});
20572057

20582058
// T547950

0 commit comments

Comments
 (0)