diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/editing/T1323684_readonlyEditorNewRow.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/editing/T1323684_readonlyEditorNewRow.ts new file mode 100644 index 000000000000..13cc125fe620 --- /dev/null +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/editing/T1323684_readonlyEditorNewRow.ts @@ -0,0 +1,170 @@ +import DataGrid from 'devextreme-testcafe-models/dataGrid'; +import { GridsEditMode } from 'devextreme/ui/data_grid'; +import { createWidget } from '../../../../helpers/createWidget'; +import url from '../../../../helpers/getPageUrl'; + +fixture.disablePageReloads`Editing - showEditorAlways cell in new row should be editable (T1323684)` + .page(url(__dirname, '../../../container.html')); + +const READONLY_CLASS = 'dx-datagrid-readonly'; +const CELL_FOCUS_DISABLED_CLASS = 'dx-cell-focus-disabled'; + +(['cell', 'batch'] as GridsEditMode[]).forEach((mode) => { + test(`showEditorAlways editor should be editable in a new row when allowUpdating is false, ${mode} mode`, async (t) => { + const dataGrid = new DataGrid('#container'); + const addRowButton = dataGrid.getHeaderPanel().getAddRowButton(); + + await t.click(addRowButton); + + const newRow = dataGrid.getDataRow(0); + await t.expect(newRow.isInserted).ok(); + + const cell = dataGrid.getDataCell(0, 1); + const editor = cell.getEditor(); + + await t + .expect(cell.element.hasClass(READONLY_CLASS)) + .notOk('showEditorAlways cell in new row should not have readonly class') + .expect(cell.element.hasClass(CELL_FOCUS_DISABLED_CLASS)) + .notOk('showEditorAlways cell in new row should not have cell-focus-disabled class'); + + await t + .click(editor.element) + .expect(cell.isFocused) + .ok('showEditorAlways cell should be focused after click') + .expect(editor.element.focused) + .ok('editor should be focused after click') + .typeText(editor.element, 'test value', { replace: true }) + .expect(editor.element.value) + .eql('test value'); + }).before(async () => createWidget('dxDataGrid', { + keyExpr: 'ID', + dataSource: [ + { ID: 1, FirstName: 'John', LastName: 'Heart' }, + { ID: 2, FirstName: 'Olivia', LastName: 'Peyton' }, + ], + showBorders: true, + editing: { + mode, + allowUpdating: false, + allowAdding: true, + }, + columns: [ + 'LastName', + { dataField: 'FirstName', showEditorAlways: true }, + ], + })); + + test(`Boolean editor should be editable in a new row when allowUpdating is false, ${mode} mode`, async (t) => { + const dataGrid = new DataGrid('#container'); + const addRowButton = dataGrid.getHeaderPanel().getAddRowButton(); + + await t.click(addRowButton); + + const newRow = dataGrid.getDataRow(0); + await t.expect(newRow.isInserted).ok(); + + const booleanCell = dataGrid.getDataCell(0, 1); + + await t + .expect(booleanCell.element.hasClass(READONLY_CLASS)) + .notOk('boolean cell in new row should not have readonly class'); + + await t + .click(booleanCell.element) + .click(booleanCell.getCheckbox()) + .expect(booleanCell.getEditor().isChecked()) + .ok('checkbox in new row should be checked after click in it'); + }).before(async () => createWidget('dxDataGrid', { + keyExpr: 'ID', + dataSource: [ + { ID: 1, Name: 'John', Active: false }, + { ID: 2, Name: 'Olivia', Active: true }, + ], + showBorders: true, + editing: { + mode, + allowUpdating: false, + allowAdding: true, + }, + columns: [ + 'Name', + { dataField: 'Active', dataType: 'boolean' }, + ], + })); + + test(`showEditorAlways editor in existing rows should remain readonly when allowUpdating is false, ${mode} mode`, async (t) => { + const dataGrid = new DataGrid('#container'); + const existingCell = dataGrid.getDataCell(0, 1); + + await t + .expect(existingCell.element.hasClass(READONLY_CLASS)) + .ok('showEditorAlways cell in existing row should have readonly class when allowUpdating is false'); + + await t + .click(existingCell.getEditor().element) + .expect(existingCell.element.hasClass(READONLY_CLASS)) + .ok('showEditorAlways cell in existing row should remain readonly after click'); + }).before(async () => createWidget('dxDataGrid', { + keyExpr: 'ID', + dataSource: [ + { ID: 1, FirstName: 'John', LastName: 'Heart' }, + { ID: 2, FirstName: 'Olivia', LastName: 'Peyton' }, + ], + showBorders: true, + editing: { + mode, + allowUpdating: false, + allowAdding: true, + }, + columns: [ + 'LastName', + { dataField: 'FirstName', showEditorAlways: true }, + ], + })); +}); + +test('showEditorAlways editor should be editable in a new row when allowUpdating is a function returning false, cell mode', async (t) => { + const dataGrid = new DataGrid('#container'); + const addRowButton = dataGrid.getHeaderPanel().getAddRowButton(); + + await t.click(addRowButton); + + const newRow = dataGrid.getDataRow(0); + await t.expect(newRow.isInserted).ok(); + + const cell = dataGrid.getDataCell(0, 1); + const editor = cell.getEditor(); + + await t + .expect(cell.element.hasClass(READONLY_CLASS)) + .notOk('showEditorAlways cell in new row should not have readonly class') + .expect(cell.element.hasClass(CELL_FOCUS_DISABLED_CLASS)) + .notOk('showEditorAlways cell in new row should not have cell-focus-disabled class'); + + await t + .click(editor.element) + .expect(cell.isFocused) + .ok('showEditorAlways cell should be focused after click') + .expect(editor.element.focused) + .ok('editor should be focused after click') + .typeText(editor.element, 'test value', { replace: true }) + .expect(editor.element.value) + .eql('test value'); +}).before(async () => createWidget('dxDataGrid', { + keyExpr: 'ID', + dataSource: [ + { ID: 1, FirstName: 'John', LastName: 'Heart' }, + { ID: 2, FirstName: 'Olivia', LastName: 'Peyton' }, + ], + showBorders: true, + editing: { + mode: 'cell' as GridsEditMode, + allowUpdating: () => false, + allowAdding: true, + }, + columns: [ + 'LastName', + { dataField: 'FirstName', showEditorAlways: true }, + ], +})); diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing.ts index 8003f1815591..3531fecd28e1 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing.ts @@ -22,8 +22,7 @@ import * as iconUtils from '@js/core/utils/icon'; import { each } from '@js/core/utils/iterator'; import { deepExtendArraySafe } from '@js/core/utils/object'; import { - isDefined, isEmptyObject, - isFunction, isObject, + isDefined, isEmptyObject, isFunction, isObject, } from '@js/core/utils/type'; import { confirm } from '@js/ui/dialog'; import { current, isFluent } from '@js/ui/themes'; @@ -2254,23 +2253,33 @@ class EditingControllerImpl extends modules.ViewController { } public getColumnTemplate(options) { - const { column } = options; - const rowIndex = options.row && options.row.rowIndex; + const { column, row, rowType } = options; + const rowIndex = row?.rowIndex; let template; + const isRowMode = this.isRowBasedEditMode(); const isRowEditing = this.isEditRow(rowIndex); const isCellEditing = this.isEditCell(rowIndex, options.columnIndex); - let editingStartOptions; - if ((column.showEditorAlways || column.setCellValue && (isRowEditing && column.allowEditing || isCellEditing)) - && (options.rowType === 'data' || options.rowType === 'detailAdaptive') && !column.command) { - const allowUpdating = this.allowUpdating(options); - if (((allowUpdating || isRowEditing) && column.allowEditing || isCellEditing) && (isRowEditing || !isRowMode)) { + const isEditableRowType = rowType === 'data' || rowType === 'detailAdaptive'; + const isEditableByRowState = isRowEditing && !!column.allowEditing; + const needsEditorTemplate = !!column.showEditorAlways + || (column.setCellValue && (isEditableByRowState || isCellEditing)); + + if (needsEditorTemplate && isEditableRowType && !column.command) { + const allowUpdating = !!this.allowUpdating(options); + const canModifyCell = (allowUpdating || isRowEditing || !!row?.isNewRow) + && !!column.allowEditing; + const isEditable = (canModifyCell || isCellEditing) && (isRowEditing || !isRowMode); + + if (isEditable) { + // eslint-disable-next-line @typescript-eslint/init-declarations + let editingStartOptions; if (column.showEditorAlways && !isRowMode) { editingStartOptions = { cancel: false, - key: options.row.isNewRow ? undefined : options.row.key, - data: options.row.data, + key: row?.isNewRow ? undefined : row.key, + data: row.data, column, }; this._isEditingStart(editingStartOptions); @@ -2282,7 +2291,7 @@ class EditingControllerImpl extends modules.ViewController { } } template = column.editCellTemplate || this._getDefaultEditorTemplate(); - } else if (column.command === 'detail' && options.rowType === 'detail' && isRowEditing) { + } else if (column.command === 'detail' && rowType === 'detail' && isRowEditing) { template = (this as any)?.getEditFormTemplate(options); }