Skip to content

Commit e578e8a

Browse files
authored
DataGrid: Fix extra editCellTemplate/onEditorPreparing call after clicking on a cell in a different row and column (T1291087) (#30721)
1 parent 2877b15 commit e578e8a

File tree

7 files changed

+213
-45
lines changed

7 files changed

+213
-45
lines changed

e2e/testcafe-devextreme/tests/dataGrid/common/editing.ts

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2812,3 +2812,153 @@ test('DataGrid - A new row is added above the existing row if the data source is
28122812
})
28132813
.after(async () => changeTheme(Themes.genericLight));
28142814
});
2815+
2816+
// T1291087
2817+
test('The editCellTemplate template should not be called after clicking on a cell in another row and column', async (t) => {
2818+
// arrange
2819+
const dataGrid = new DataGrid('#container');
2820+
const firstCellOfFirstRow = dataGrid.getDataCell(0, 0);
2821+
const secondCellOfSecondRow = dataGrid.getDataCell(1, 1);
2822+
const checkEditCellTemplateCallArgs = async () => {
2823+
const templateCallArgs = await ClientFunction(() => (window as any).editCellTemplateCallArgs)();
2824+
2825+
await t
2826+
.expect(templateCallArgs.length)
2827+
.eql(1, 'editCellTemplate should be called only once')
2828+
.expect(templateCallArgs[0])
2829+
.eql('Column1', 'editCellTemplate should be called for the first column only');
2830+
};
2831+
2832+
// act
2833+
await t.click(firstCellOfFirstRow.element);
2834+
2835+
// assert
2836+
await t
2837+
.expect(firstCellOfFirstRow.isEditCell)
2838+
.ok('The first cell of the first row should be in edit mode')
2839+
.expect(firstCellOfFirstRow.getEditor().element.focused)
2840+
.ok('The first cell of the first row should be focused');
2841+
2842+
await checkEditCellTemplateCallArgs();
2843+
2844+
// act
2845+
await t.click(secondCellOfSecondRow.element);
2846+
2847+
// assert
2848+
await t
2849+
.expect(secondCellOfSecondRow.isEditCell)
2850+
.ok('The second cell of the second row should be in edit mode')
2851+
.expect(secondCellOfSecondRow.getEditor().element.focused)
2852+
.ok('The second cell of the second row should be focused');
2853+
2854+
await checkEditCellTemplateCallArgs();
2855+
}).before(async () => {
2856+
await ClientFunction(() => {
2857+
(window as any).editCellTemplateCallArgs = [];
2858+
})();
2859+
2860+
return createWidget('dxDataGrid', {
2861+
dataSource: [
2862+
{
2863+
ID: 1, Column1: 'a', Column2: 'b', Column3: 'c',
2864+
},
2865+
{
2866+
ID: 2, Column1: 'd', Column2: 'e', Column3: 'f',
2867+
},
2868+
{
2869+
ID: 3, Column1: 'g', Column2: 'h', Column3: 'i',
2870+
},
2871+
],
2872+
keyExpr: 'ID',
2873+
columns: [{
2874+
dataField: 'Column1',
2875+
editCellTemplate(_, cellInfo) {
2876+
(window as any).editCellTemplateCallArgs.push(cellInfo.column.dataField);
2877+
2878+
return ($('<div>') as any).dxTextBox({
2879+
value: cellInfo.value,
2880+
onValueChanged: (args) => cellInfo.setValue(args.value),
2881+
});
2882+
},
2883+
}, 'Column2', 'Column3'],
2884+
showBorders: true,
2885+
editing: { mode: 'batch', allowUpdating: true },
2886+
});
2887+
}).after(async () => {
2888+
await ClientFunction(() => {
2889+
delete (window as any).editCellTemplateCallArgs;
2890+
})();
2891+
});
2892+
2893+
// T1291087
2894+
test('The onEditorPreparing event should be called once after clicking on a cell in another row and column', async (t) => {
2895+
// arrange
2896+
const dataGrid = new DataGrid('#container');
2897+
const firstCellOfFirstRow = dataGrid.getDataCell(0, 0);
2898+
const secondCellOfSecondRow = dataGrid.getDataCell(1, 1);
2899+
const checkOnEditorPreparingCallArgs = async (expectedCallCount, expectedArgs) => {
2900+
const eventCallArgs = await ClientFunction(() => (window as any).onEditorPreparingCallArgs)();
2901+
2902+
await t
2903+
.expect(eventCallArgs.length)
2904+
.eql(expectedCallCount, 'number of the onEditorPreparing event calls is correct')
2905+
.expect(eventCallArgs[expectedCallCount - 1])
2906+
.eql(expectedArgs, 'onEditorPreparing event called for expected cell');
2907+
};
2908+
2909+
// act
2910+
await t.click(firstCellOfFirstRow.element);
2911+
2912+
// assert
2913+
await t
2914+
.expect(firstCellOfFirstRow.isEditCell)
2915+
.ok('The first cell of the first row should be in edit mode')
2916+
.expect(firstCellOfFirstRow.getEditor().element.focused)
2917+
.ok('The first cell of the first row should be focused');
2918+
2919+
await checkOnEditorPreparingCallArgs(1, { dataField: 'Column1', rowIndex: 0 });
2920+
2921+
// act
2922+
await t.click(secondCellOfSecondRow.element);
2923+
2924+
// assert
2925+
await t
2926+
.expect(secondCellOfSecondRow.isEditCell)
2927+
.ok('The second cell of the second row should be in edit mode')
2928+
.expect(secondCellOfSecondRow.getEditor().element.focused)
2929+
.ok('The second cell of the second row should be focused');
2930+
2931+
await checkOnEditorPreparingCallArgs(2, { dataField: 'Column2', rowIndex: 1 });
2932+
}).before(async () => {
2933+
await ClientFunction(() => {
2934+
(window as any).onEditorPreparingCallArgs = [];
2935+
})();
2936+
2937+
return createWidget('dxDataGrid', {
2938+
dataSource: [
2939+
{
2940+
ID: 1, Column1: 'a', Column2: 'b', Column3: 'c',
2941+
},
2942+
{
2943+
ID: 2, Column1: 'd', Column2: 'e', Column3: 'f',
2944+
},
2945+
{
2946+
ID: 3, Column1: 'g', Column2: 'h', Column3: 'i',
2947+
},
2948+
],
2949+
keyExpr: 'ID',
2950+
columns: ['Column1', 'Column2', 'Column3'],
2951+
showBorders: true,
2952+
editing: { mode: 'batch', allowUpdating: true },
2953+
onEditorPreparing(e) {
2954+
(window as any).onEditorPreparingCallArgs.push({
2955+
dataField: e.dataField,
2956+
rowIndex: e.row?.rowIndex,
2957+
});
2958+
},
2959+
});
2960+
}).after(async () => {
2961+
await ClientFunction(() => {
2962+
delete (window as any).onEditorPreparingCallArgs;
2963+
})();
2964+
});

e2e/testcafe-devextreme/tests/dataGrid/common/keyboardNavigation/keyboardNavigation.functional.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ test('Changing keyboardNavigation options should not invalidate the entire conte
7575
.expect(ClientFunction(() => (window as any).invalidateCounter)())
7676
.eql(0)
7777
.expect(ClientFunction(() => (window as any).renderTableCounter)())
78-
.eql(10);
78+
.eql(9);
7979
}).before(async () => {
8080
await ClientFunction(() => {
8181
(window as any).invalidateCounter = 0;

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,11 @@ interface HandleDataChangedArguments {
7373

7474
type UserData = Record<string, unknown>;
7575

76-
interface Item {
76+
export interface Item {
7777
rowType: 'data' | 'group' | 'groupFooter' | 'detailAdaptive';
7878
data: UserData;
79+
key: unknown;
80+
oldData?: UserData;
7981
dataIndex?: number;
8082
values?: unknown[];
8183
visible?: boolean;
@@ -85,8 +87,8 @@ interface Item {
8587
rowIndex?: number;
8688
cells?: unknown[];
8789
loadIndex?: number;
88-
key: unknown;
8990
isSelected?: boolean;
91+
removed?: boolean;
9092
}
9193

9294
export type Filter = any;

packages/devextreme/js/__internal/grids/grid_core/editing/m_editing.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ import {
9292
isEditingCell,
9393
isEditingOrShowEditorAlwaysDataCell,
9494
} from './m_editing_utils';
95+
import type { NormalizedEditCellOptions } from './types';
9596

9697
class EditingControllerImpl extends modules.ViewController {
9798
protected _columnsController!: Controllers['columns'];
@@ -652,17 +653,6 @@ class EditingControllerImpl extends modules.ViewController {
652653
}
653654
}
654655

655-
private _setEditRowKeyByIndex(rowIndex, silent) {
656-
const key = this._dataController.getKeyByRowIndex(rowIndex);
657-
658-
if (key === undefined) {
659-
this._dataController.fireError('E1043');
660-
return;
661-
}
662-
663-
this._setEditRowKey(key, silent);
664-
}
665-
666656
public getEditRowIndex() {
667657
return this._getVisibleEditRowIndex();
668658
}
@@ -2477,6 +2467,8 @@ class EditingControllerImpl extends modules.ViewController {
24772467

24782468
protected _isRowDeleteAllowed(): any {}
24792469

2470+
protected _prepareEditCell(parameters: NormalizedEditCellOptions): boolean { return false; }
2471+
24802472
public shouldHighlightCell(parameters) {
24812473
const cellModified = this.isCellModified(parameters);
24822474
return cellModified

packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_cell_based.ts

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
} from './const';
3434
import type { EditingController } from './m_editing';
3535
import { isEditable } from './m_editing_utils';
36+
import type { NormalizedEditCellOptions } from './types';
3637

3738
export interface ICellBasedEditingControllerExtender {
3839
// eslint-disable-next-line @typescript-eslint/method-signature-style
@@ -234,36 +235,31 @@ const editingControllerExtender = (Base: ModuleType<EditingController>) => class
234235
return coreResult !== undefined ? coreResult : d.promise();
235236
}
236237

237-
private _editCellCore(options) {
238-
const dataController = this._dataController;
239-
const isEditByOptionChanged = isDefined(options.oldColumnIndex) || isDefined(options.oldRowIndex);
238+
private _editCellCore(options): boolean | DeferredObj<boolean> | undefined {
239+
const editCellOptions: NormalizedEditCellOptions = this._getNormalizedEditCellOptions(options);
240240
const {
241-
columnIndex, rowIndex, column, item,
242-
} = this._getNormalizedEditCellOptions(options) as any;
243-
const params = {
244-
data: item?.data,
245-
cancel: false,
241+
columnIndex,
242+
rowIndex,
246243
column,
247-
};
244+
item,
245+
} = editCellOptions;
248246

249247
if (item.key === undefined) {
250248
this._dataController.fireError('E1043');
251249
return;
252250
}
253251

254252
if (column && (item.rowType === 'data' || item.rowType === 'detailAdaptive') && !item.removed && this.isCellOrBatchEditMode()) {
255-
if (!isEditByOptionChanged && this.isEditCell(rowIndex, columnIndex)) {
253+
if (this.isEditCell(rowIndex, columnIndex)) {
256254
return true;
257255
}
258256

259-
const editRowIndex = rowIndex + dataController.getRowIndexOffset();
260-
261257
return when(this._beforeEditCell(rowIndex, columnIndex, item)).done((cancel) => {
262258
if (cancel) {
263259
return;
264260
}
265261

266-
if (!this._prepareEditCell(params, item, columnIndex, editRowIndex)) {
262+
if (!this._prepareEditCell(editCellOptions)) {
267263
this._processCanceledEditingCell();
268264
}
269265
});
@@ -294,8 +290,16 @@ const editingControllerExtender = (Base: ModuleType<EditingController>) => class
294290
}
295291

296292
private _getNormalizedEditCellOptions({
297-
oldColumnIndex, oldRowIndex, columnIndex, rowIndex,
298-
}) {
293+
oldColumnIndex,
294+
oldRowIndex,
295+
columnIndex,
296+
rowIndex,
297+
}: {
298+
oldColumnIndex: number;
299+
oldRowIndex: number;
300+
columnIndex: number;
301+
rowIndex: number;
302+
}): NormalizedEditCellOptions {
299303
const columnsController = this._columnsController;
300304
const visibleColumns = columnsController.getVisibleColumns();
301305
const items = this._dataController.items();
@@ -324,22 +328,31 @@ const editingControllerExtender = (Base: ModuleType<EditingController>) => class
324328
};
325329
}
326330

327-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
328-
private _prepareEditCell(params, item, editColumnIndex, editRowIndex) {
329-
if (!item.isNewRow) {
330-
params.key = item.key;
331-
}
331+
protected _prepareEditCell({
332+
item,
333+
column,
334+
oldColumn,
335+
columnIndex,
336+
oldRowIndex,
337+
}: NormalizedEditCellOptions): boolean {
338+
const editingStartParams = {
339+
data: item?.data,
340+
cancel: false,
341+
column,
342+
key: !item.isNewRow ? item.key : undefined,
343+
};
332344

333-
if (this._isEditingStart(params)) {
345+
if (this._isEditingStart(editingStartParams)) {
334346
return false;
335347
}
336348

337349
this._pageIndex = this._dataController.pageIndex();
338350

339-
this._setEditRowKey(item.key);
340-
this._setEditColumnNameByIndex(editColumnIndex);
351+
this._setEditRowKey(item.key, true);
352+
this._setEditColumnNameByIndex(columnIndex, true);
353+
this._repaintEditCell(column, oldColumn, oldRowIndex);
341354

342-
if (!params.column.showEditorAlways) {
355+
if (!column.showEditorAlways) {
343356
this._addInternalData({
344357
key: item.key,
345358
oldData: item.oldData ?? item.data,
@@ -419,7 +432,6 @@ const editingControllerExtender = (Base: ModuleType<EditingController>) => class
419432
}
420433
}
421434

422-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
423435
protected _prepareChange(options, value, text) {
424436
const $cellElement = $(options.cellElement);
425437

@@ -516,7 +528,6 @@ const editingControllerExtender = (Base: ModuleType<EditingController>) => class
516528
}
517529
}
518530

519-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
520531
protected _allowRowAdding(params) {
521532
if (this.isBatchEditMode()) {
522533
return true;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { Column } from '../columns_controller/m_columns_controller';
2+
import type { Item } from '../data_controller/m_data_controller';
3+
4+
export interface NormalizedEditCellOptions {
5+
item: Item;
6+
oldColumn: Column;
7+
column: Column;
8+
columnIndex: number;
9+
oldRowIndex: number;
10+
rowIndex: number;
11+
}

packages/devextreme/js/__internal/grids/grid_core/validating/m_validating.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import type { RowsView } from '@ts/grids/grid_core/views/m_rows_view';
3333

3434
import { EDITORS_INPUT_SELECTOR } from '../editing/const';
3535
import type { EditingController } from '../editing/m_editing';
36+
import type { NormalizedEditCellOptions } from '../editing/types';
3637
import modules from '../m_modules';
3738
import type { ModuleType } from '../m_types';
3839
import gridCoreUtils from '../m_utils';
@@ -710,12 +711,13 @@ export const validatingEditingExtender = (Base: ModuleType<EditingController>) =
710711
super._validateEditFormAfterUpdate.apply(this, arguments as any);
711712
}
712713

713-
private _prepareEditCell(params) {
714-
// @ts-expect-error
715-
const isNotCanceled = super._prepareEditCell.apply(this, arguments as any);
714+
protected _prepareEditCell(parameters: NormalizedEditCellOptions): boolean {
715+
const { column, item } = parameters;
716+
const isNotCanceled: boolean = super._prepareEditCell(parameters);
717+
const key = !item.isNewRow ? item.key : undefined;
716718

717-
if (isNotCanceled && params.column.showEditorAlways) {
718-
this._validatingController.updateValidationState({ key: params.key });
719+
if (isNotCanceled && column.showEditorAlways) {
720+
this._validatingController.updateValidationState({ key });
719721
}
720722

721723
return isNotCanceled;

0 commit comments

Comments
 (0)