diff --git a/packages/devextreme/js/__internal/grids/data_grid/module_not_extended/validating.ts b/packages/devextreme/js/__internal/grids/data_grid/module_not_extended/validating.ts index 2696f484df08..7b08ed5e1451 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/module_not_extended/validating.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/module_not_extended/validating.ts @@ -1,4 +1,4 @@ -import { validatingModule } from '@ts/grids/grid_core/validating/m_validating'; +import { validatingModule } from '@ts/grids/grid_core/validating/validating_module'; import gridCore from '../m_core'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts index bb75ed2672c3..3d1735dbfdba 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts @@ -42,7 +42,6 @@ import type { import gridCoreUtils from '@ts/grids/grid_core/m_utils'; import type { SelectionController } from '@ts/grids/grid_core/selection/m_selection'; import type { StateStoringController } from '@ts/grids/grid_core/state_storing/m_state_storing_core'; -import type { ValidatingController } from '@ts/grids/grid_core/validating/m_validating'; import type { VirtualScrollController } from '@ts/grids/grid_core/virtual_scrolling/m_virtual_scrolling_core'; import { DataHelperMixin } from './data_helper_mixin'; @@ -154,8 +153,6 @@ export class DataController extends DataHelperMixin(modules.Controller) { protected _stateStoringController!: StateStoringController; - protected _validatingController!: ValidatingController; - private loadErrorHandlerProxy!: (e: Error | string) => void; private dataPushedHandlerProxy!: (changes: StoreChange[]) => void; @@ -177,7 +174,6 @@ export class DataController extends DataHelperMixin(modules.Controller) { this._headerFilterController = this.getController('headerFilter'); this._selectionController = this.getController('selection'); this._stateStoringController = this.getController('stateStoring'); - this._validatingController = this.getController('validating'); this._isPaging = false; this._currentOperationTypes = null; diff --git a/packages/devextreme/js/__internal/grids/grid_core/validating/const.ts b/packages/devextreme/js/__internal/grids/grid_core/validating/const.ts new file mode 100644 index 000000000000..7cde2d2ce9de --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/validating/const.ts @@ -0,0 +1,22 @@ +import { isDefined } from '@js/core/utils/type'; + +export const INVALIDATE_CLASS = 'invalid'; + +export const VALIDATION_STATUS = { + valid: 'valid', + invalid: 'invalid', + pending: 'pending', +} as const; + +export type ValidationStatus = typeof VALIDATION_STATUS[keyof typeof VALIDATION_STATUS]; + +export const VALIDATION_CANCELLED = 'cancel'; + +export interface CellValidationResult { + status?: ValidationStatus; + disabledPendingId?: unknown; +} + +export const validationResultIsValid = ( + result: unknown, +): result is CellValidationResult => isDefined(result) && result !== VALIDATION_CANCELLED; diff --git a/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts new file mode 100644 index 000000000000..74704393f089 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/validating/extenders/validating_data_controller.ts @@ -0,0 +1,104 @@ +import $ from '@js/core/renderer'; +import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; +import type { Cell, ProcessedItem } from '@ts/grids/grid_core/data_controller/types'; +import type { ModuleType } from '@ts/grids/grid_core/m_types'; + +import type { CellValidationResult, ValidationStatus } from '../const'; +import { + INVALIDATE_CLASS, + VALIDATION_STATUS, + validationResultIsValid, +} from '../const'; + +type ValidationResult = CellValidationResult | string | undefined; + +interface ValidationData { + isValid?: boolean; +} + +type ValidatedCell = Cell & { + validationStatus?: ValidationStatus; + cellElement?: Element; +}; + +interface ValidatingControllerReader { + getCellValidationResult: (options: { rowKey: unknown; columnIndex: number }) => ValidationResult; + getValidationData: (key: unknown) => ValidationData | undefined; +} + +export const validatingDataControllerExtender = ( + Base: ModuleType, +): ModuleType => class ValidatingDataControllerExtender extends Base { + protected _validatingController!: ValidatingControllerReader; + + public init(): void { + this._validatingController = this.getController('validating'); + super.init(); + } + + private _getValidationStatus(validationResult: ValidationResult): string { + if (!validationResultIsValid(validationResult)) { + return validationResult ?? VALIDATION_STATUS.valid; + } + + return validationResult.status ?? VALIDATION_STATUS.valid; + } + + private _isRowEditStateChanged( + oldRow: ProcessedItem, + newRow: ProcessedItem, + columnIndex: number, + ): boolean { + const cell = oldRow.cells?.[columnIndex]; + const hasValidationRules = !!cell?.column?.validationRules?.length; + + return oldRow.isEditing !== newRow.isEditing && hasValidationRules; + } + + private _isCellValidationStateChanged( + oldRow: ProcessedItem, + newRow: ProcessedItem, + columnIndex: number, + ): boolean { + const cell = oldRow.cells?.[columnIndex] as ValidatedCell | undefined; + + const oldValidationStatus = this._getValidationStatus({ status: cell?.validationStatus }); + const newValidationStatus = this._getValidationStatus( + this._validatingController.getCellValidationResult({ rowKey: oldRow.key, columnIndex }), + ); + const rowIsModified = JSON.stringify(newRow.modifiedValues) + !== JSON.stringify(oldRow.modifiedValues); + const validationStatusChanged = oldValidationStatus !== newValidationStatus && rowIsModified; + + if (validationStatusChanged) { + return true; + } + + const validationData = this._validatingController.getValidationData(oldRow.key); + const cellIsMarkedAsInvalid = $(cell?.cellElement) + .hasClass(this.addWidgetPrefix(INVALIDATE_CLASS)); + + return !!validationData?.isValid && cellIsMarkedAsInvalid; + } + + protected _isCellChanged( + oldRow: ProcessedItem, + newRow: ProcessedItem, + visibleRowIndex: number, + columnIndex: number, + isLiveUpdate?: boolean, + ): boolean { + const rowEditStateChanged = this._isRowEditStateChanged(oldRow, newRow, columnIndex); + const cellValidationStateChanged = this._isCellValidationStateChanged( + oldRow, + newRow, + columnIndex, + ); + + if (rowEditStateChanged || cellValidationStateChanged) { + return true; + } + + return super._isCellChanged(oldRow, newRow, visibleRowIndex, columnIndex, isLiveUpdate); + } +}; diff --git a/packages/devextreme/js/__internal/grids/grid_core/validating/m_validating.ts b/packages/devextreme/js/__internal/grids/grid_core/validating/m_validating.ts index e9d907b693b1..aef946b7f079 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/validating/m_validating.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/validating/m_validating.ts @@ -27,7 +27,6 @@ import Validator from '@js/ui/validator'; import errors from '@js/ui/widget/ui.errors'; import { focused } from '@ts/core/utils/m_selectors'; import type { ColumnsController } from '@ts/grids/grid_core/columns_controller/m_columns_controller'; -import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; import type { EditorFactory } from '@ts/grids/grid_core/editor_factory/m_editor_factory'; import type { RowsView } from '@ts/grids/grid_core/views/m_rows_view'; @@ -37,8 +36,13 @@ import type { NormalizedEditCellOptions } from '../editing/types'; import modules from '../m_modules'; import type { ModuleType } from '../m_types'; import gridCoreUtils from '../m_utils'; +import { + INVALIDATE_CLASS, + VALIDATION_CANCELLED, + VALIDATION_STATUS, + validationResultIsValid, +} from './const'; -const INVALIDATE_CLASS = 'invalid'; const REVERT_TOOLTIP_CLASS = 'revert-tooltip'; const INVALID_MESSAGE_CLASS = 'dx-invalid-message'; const INVALID_MESSAGE_ID = 'dxInvalidMessage'; @@ -64,19 +68,8 @@ const FORM_BASED_MODES = [EDIT_MODE_POPUP, EDIT_MODE_FORM]; const COMMAND_TRANSPARENT = 'transparent'; -const VALIDATION_STATUS = { - valid: 'valid', - invalid: 'invalid', - pending: 'pending', -}; - const EDIT_DATA_INSERT_TYPE = 'insert'; const EDIT_DATA_REMOVE_TYPE = 'remove'; -const VALIDATION_CANCELLED = 'cancel'; - -const validationResultIsValid = function (result) { - return isDefined(result) && result !== VALIDATION_CANCELLED; -}; const cellValueShouldBeValidated = function (value, rowOptions) { return value !== undefined || (value === undefined && rowOptions && !rowOptions.isNewRow); @@ -149,6 +142,10 @@ export class ValidatingController extends modules.Controller { return !!validationData && !!validationData.validated; } + public getValidationData(key) { + return this._getValidationData(key); + } + public _getValidationData(key, create?) { const keyHash = getKeyHash(key); const isObjectKeyHash = isObject(keyHash); @@ -1519,37 +1516,6 @@ export const validatingEditorFactoryExtender = (Base: ModuleType) } }; -export const validatingDataControllerExtender = (Base: ModuleType) => class ValidatingDataControllerExtender extends Base { - private _getValidationStatus(validationResult) { - const validationStatus = validationResultIsValid(validationResult) ? validationResult.status : validationResult; - - return validationStatus || VALIDATION_STATUS.valid; - } - - protected _isCellChanged(oldRow, newRow, visibleRowIndex, columnIndex, isLiveUpdate) { - const cell = oldRow.cells?.[columnIndex]; - const oldValidationStatus = this._getValidationStatus({ status: cell?.validationStatus }); - const validationResult = this._validatingController.getCellValidationResult({ - rowKey: oldRow.key, - columnIndex, - }); - const validationData = this._validatingController._getValidationData(oldRow.key); - const newValidationStatus = this._getValidationStatus(validationResult); - const rowIsModified = JSON.stringify(newRow.modifiedValues) !== JSON.stringify(oldRow.modifiedValues); - const validationStatusChanged = oldValidationStatus !== newValidationStatus && rowIsModified; - const cellIsMarkedAsInvalid = $(cell?.cellElement).hasClass(this.addWidgetPrefix(INVALIDATE_CLASS)); - const hasValidationRules = cell?.column.validationRules?.length; - const rowEditStateChanged = oldRow.isEditing !== newRow.isEditing && hasValidationRules; - const cellValidationStateChanged = validationStatusChanged || validationData.isValid && cellIsMarkedAsInvalid; - - if (rowEditStateChanged || cellValidationStateChanged) { - return true; - } - - return super._isCellChanged.apply(this, arguments as any); - } -}; - export const validatingRowsViewExtender = (Base: ModuleType) => class ValidatingRowsViewExtender extends Base { public updateFreeSpaceRowHeight($table) { const that = this; @@ -1621,28 +1587,3 @@ export const validatingRowsViewExtender = (Base: ModuleType) => class }); } }; - -export const validatingModule = { - defaultOptions() { - return { - editing: { - texts: { - validationCancelChanges: messageLocalization.format('dxDataGrid-validationCancelChanges'), - }, - }, - }; - }, - controllers: { - validating: ValidatingController, - }, - extenders: { - controllers: { - editing: validatingEditingExtender, - editorFactory: validatingEditorFactoryExtender, - data: validatingDataControllerExtender, - }, - views: { - rowsView: validatingRowsViewExtender, - }, - }, -}; diff --git a/packages/devextreme/js/__internal/grids/grid_core/validating/validating_module.ts b/packages/devextreme/js/__internal/grids/grid_core/validating/validating_module.ts new file mode 100644 index 000000000000..7510fbec101b --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/validating/validating_module.ts @@ -0,0 +1,39 @@ +import messageLocalization from '@js/common/core/localization/message'; +import type { EditingTextsBase } from '@js/common/grids'; + +import { validatingDataControllerExtender } from './extenders/validating_data_controller'; +import { + ValidatingController, + validatingEditingExtender, + validatingEditorFactoryExtender, + validatingRowsViewExtender, +} from './m_validating'; + +interface ValidatingModuleOptions { + editing: { texts: Pick }; +} + +export const validatingModule = { + defaultOptions(): ValidatingModuleOptions { + return { + editing: { + texts: { + validationCancelChanges: messageLocalization.format('dxDataGrid-validationCancelChanges'), + }, + }, + }; + }, + controllers: { + validating: ValidatingController, + }, + extenders: { + controllers: { + editing: validatingEditingExtender, + editorFactory: validatingEditorFactoryExtender, + data: validatingDataControllerExtender, + }, + views: { + rowsView: validatingRowsViewExtender, + }, + }, +}; diff --git a/packages/devextreme/js/__internal/grids/tree_list/m_validating.ts b/packages/devextreme/js/__internal/grids/tree_list/m_validating.ts index e773c6459544..b088f3532074 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/m_validating.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/m_validating.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { validatingModule } from '@ts/grids/grid_core/validating/m_validating'; +import { validatingModule } from '@ts/grids/grid_core/validating/validating_module'; import type { EditingController } from '../grid_core/editing/m_editing'; import type { ModuleType } from '../grid_core/m_types';