diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.ts index 859b58d185bc..c0c5eb658d98 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.ts @@ -3,7 +3,6 @@ import dateLocalization from '@js/common/core/localization/date'; import messageLocalization from '@js/common/core/localization/message'; import { DataSource } from '@js/common/data/data_source/data_source'; import { normalizeDataSourceOptions } from '@js/common/data/data_source/utils'; -import config from '@js/core/config'; import $ from '@js/core/renderer'; import type { Callback } from '@js/core/utils/callbacks'; import Callbacks from '@js/core/utils/callbacks'; @@ -1442,40 +1441,6 @@ export class ColumnsController extends modules.Controller { } } - public updateFilter(filter, remoteFiltering, columnIndex?, filterValue?) { - const that = this; - - if (!Array.isArray(filter)) return filter; - - filter = extend([], filter); - - columnIndex = filter.columnIndex !== undefined ? filter.columnIndex : columnIndex; - filterValue = filter.filterValue !== undefined ? filter.filterValue : filterValue; - - if (isString(filter[0]) && filter[0] !== '!') { - const column = that.columnOption(filter[0]); - - if (remoteFiltering) { - if (config().forceIsoDateParsing && column && column.serializeValue && filter.length > 1) { - filter[filter.length - 1] = column.serializeValue(filter[filter.length - 1], 'filter'); - } - } else if (column && column.selector) { - filter[0] = column.selector; - filter[0].columnIndex = column.index; - } - } else if (isFunction(filter[0])) { - filter[0].columnIndex = columnIndex; - filter[0].filterValue = filterValue; - filter[0].selectedFilterOperation = filter.selectedFilterOperation; - } - - for (let i = 0; i < filter.length; i++) { - filter[i] = that.updateFilter(filter[i], remoteFiltering, columnIndex, filterValue); - } - - return filter; - } - public columnCount() { return this._columns ? this._columns.length : 0; } diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/types.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/types.ts index fdf2a70f43eb..61555a36ada5 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/types.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/types.ts @@ -1,4 +1,5 @@ import type { ColumnAIOptions, ColumnBase } from '@js/common/grids'; +import type { RawItemData } from '@ts/grids/grid_core/data_source_adapter/types'; import type { COLUMN_CHOOSER_LOCATION, GROUP_LOCATION, HEADERS_LOCATION, USER_STATE_FIELD_NAMES, @@ -19,10 +20,18 @@ export type ColumnUserState = Pick unknown) & { + columnIndex?: number; + filterValue?: unknown; + selectedFilterOperation?: unknown; + originalCallback?: unknown; +}; + export interface InternalColumnOptions { parseValue?: (text: string) => unknown; deserializeValue?: (value: unknown) => unknown; - serializeValue?: (value: unknown) => unknown; + serializeValue?: (value: unknown, target?: string) => unknown; + selector?: ColumnSelector; index?: number; groupIndex?: number; type?: string; 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 4991d4ff46d7..dd693dd2d3ec 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 @@ -341,7 +341,7 @@ export class DataController extends modules.Controller { const isRemoteFiltering = this._dataSource.remoteOperations().filtering || returnDataField; - combined = this._columnsController.updateFilter(combined, isRemoteFiltering); + combined = this.filterController.normalizeFilterSelectors(combined, isRemoteFiltering); return combined; } diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.update_filter.test.ts b/packages/devextreme/js/__internal/grids/grid_core/filter/__tests__/filter_controller.normalize_filter_selectors.test.ts similarity index 68% rename from packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.update_filter.test.ts rename to packages/devextreme/js/__internal/grids/grid_core/filter/__tests__/filter_controller.normalize_filter_selectors.test.ts index 2f0122ced85b..5663300d6b45 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.update_filter.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/filter/__tests__/filter_controller.normalize_filter_selectors.test.ts @@ -14,6 +14,8 @@ import { beforeTest, createDataGrid, } from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; +import type { ColumnSelector } from '@ts/grids/grid_core/columns_controller/types'; +import type { DataFilter } from '@ts/grids/grid_core/data_controller/types'; const DATA = [ { id: 1, name: 'Alex', age: 15 }, @@ -35,14 +37,15 @@ const createGrid = (): Promise<{ columns: ['name', 'age'], }); -const updateFilter = ( +const normalizeFilterSelectors = ( instance: DataGridInstance, filter: unknown, remoteFiltering: boolean, - // eslint-disable-next-line @typescript-eslint/no-explicit-any -): any => instance.getController('columns').updateFilter(filter, remoteFiltering); +): unknown => instance + .getController('filter') + .normalizeFilterSelectors(filter as DataFilter, remoteFiltering); -describe('ColumnsController.updateFilter', () => { +describe('FilterController.normalizeFilterSelectors', () => { beforeEach(beforeTest); afterEach(afterTest); @@ -51,7 +54,7 @@ describe('ColumnsController.updateFilter', () => { const { instance } = await createGrid(); const nameColumn = instance.getController('columns').getVisibleColumns()[0]; - const result = updateFilter(instance, ['name', '=', 'Alex'], false); + const result = normalizeFilterSelectors(instance, ['name', '=', 'Alex'], false) as unknown[]; expect(result[0]).toBe(nameColumn.selector); }); @@ -60,30 +63,30 @@ describe('ColumnsController.updateFilter', () => { const { instance } = await createGrid(); const columns = instance.getController('columns').getVisibleColumns(); - updateFilter(instance, ['age', '=', 15], false); + normalizeFilterSelectors(instance, ['age', '=', 15], false); - expect((columns[1].selector as TaggedSelector).columnIndex).toBe(columns[1].index); + expect(columns[1].selector?.columnIndex).toBe(columns[1].index); }); it('should walk nested groups', async () => { const { instance } = await createGrid(); const columns = instance.getController('columns').getVisibleColumns(); - const result = updateFilter( + const result = normalizeFilterSelectors( instance, [['name', '=', 'Alex'], 'and', ['age', '=', 15]], false, - ); + ) as unknown[]; - expect(result[0][0]).toBe(columns[0].selector); + expect((result[0] as unknown[])[0]).toBe(columns[0].selector); expect(result[1]).toBe('and'); - expect(result[2][0]).toBe(columns[1].selector); + expect((result[2] as unknown[])[0]).toBe(columns[1].selector); }); it('should leave an unknown field alone', async () => { const { instance } = await createGrid(); - expect(updateFilter(instance, ['unknown', '=', 1], false)).toEqual(['unknown', '=', 1]); + expect(normalizeFilterSelectors(instance, ['unknown', '=', 1], false)).toEqual(['unknown', '=', 1]); }); }); @@ -91,7 +94,7 @@ describe('ColumnsController.updateFilter', () => { it('should keep the data fields', async () => { const { instance } = await createGrid(); - expect(updateFilter(instance, ['name', '=', 'Alex'], true)).toEqual(['name', '=', 'Alex']); + expect(normalizeFilterSelectors(instance, ['name', '=', 'Alex'], true)).toEqual(['name', '=', 'Alex']); }); }); @@ -104,7 +107,7 @@ describe('ColumnsController.updateFilter', () => { filter.filterValue = 'ZZ'; filter.selectedFilterOperation = 'between'; - const result = updateFilter(instance, filter, false); + const result = normalizeFilterSelectors(instance, filter, false) as TaggedSelector; expect(result.columnIndex).toBe(7); expect(result.filterValue).toBe('ZZ'); @@ -113,18 +116,18 @@ describe('ColumnsController.updateFilter', () => { it('should pass columnIndex and filterValue down but not selectedFilterOperation', async () => { const { instance } = await createGrid(); - const customSelector = (): number => 1; + const customSelector: ColumnSelector = (): number => 1; const filter = extend([], [[customSelector, '=', 'Alex']]); filter.columnIndex = 3; filter.filterValue = 'inherited'; filter.selectedFilterOperation = 'between'; - updateFilter(instance, filter, false); + normalizeFilterSelectors(instance, filter, false); - expect((customSelector as TaggedSelector).columnIndex).toBe(3); - expect((customSelector as TaggedSelector).filterValue).toBe('inherited'); - expect((customSelector as TaggedSelector).selectedFilterOperation).toBeUndefined(); + expect(customSelector.columnIndex).toBe(3); + expect(customSelector.filterValue).toBe('inherited'); + expect(customSelector.selectedFilterOperation).toBeUndefined(); }); }); }); diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter/filter_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/filter/filter_controller.ts index dd6e280b862f..9e83eaffe8c7 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/filter/filter_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/filter/filter_controller.ts @@ -1,9 +1,18 @@ import type { LangParams } from '@js/common/data'; +import config from '@js/core/config'; +import { extend } from '@js/core/utils/extend'; +import { isFunction, isString } from '@js/core/utils/type'; import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { DataFilter } from '@ts/grids/grid_core/data_controller/types'; import modules from '@ts/grids/grid_core/m_modules'; import type { Controllers } from '@ts/grids/grid_core/m_types'; +type TaggedFilter = unknown[] & { + columnIndex?: number; + filterValue?: unknown; + selectedFilterOperation?: unknown; +}; + export class FilterController extends modules.Controller { protected columnsController!: Controllers['columns']; @@ -31,4 +40,71 @@ export class FilterController extends modules.Controller { public getAdditionalFilter(excludedColumn?: Column | null): DataFilter { return null; } + + public normalizeFilterSelectors( + filter: DataFilter, + remoteFiltering?: boolean, + columnIndex?: number, + filterValue?: unknown, + ): DataFilter { + return this.normalizeNode(filter, remoteFiltering, columnIndex, filterValue) as DataFilter; + } + + private normalizeNode( + node: unknown, + remoteFiltering: boolean | undefined, + columnIndex: number | undefined, + filterValue: unknown, + ): unknown { + if (!Array.isArray(node)) { + return node; + } + + const normalized = extend([], node) as TaggedFilter; + const nestedColumnIndex = normalized.columnIndex ?? columnIndex; + const nestedFilterValue = normalized.filterValue !== undefined + ? normalized.filterValue + : filterValue; + const head = normalized[0]; + + if (isString(head) && head !== '!') { + this.normalizeDataFieldSelector(normalized, head, remoteFiltering); + } else if (isFunction(head)) { + Object.assign(head, { + columnIndex: nestedColumnIndex, + filterValue: nestedFilterValue, + selectedFilterOperation: normalized.selectedFilterOperation, + }); + } + + for (let i = 0; i < normalized.length; i += 1) { + normalized[i] = this.normalizeNode( + normalized[i], + remoteFiltering, + nestedColumnIndex, + nestedFilterValue, + ); + } + + return normalized; + } + + private normalizeDataFieldSelector( + filter: TaggedFilter, + dataField: string, + remoteFiltering: boolean | undefined, + ): void { + const column = this.columnsController.columnOption(dataField) as Column | undefined; + + if (remoteFiltering) { + const lastIndex = filter.length - 1; + + if (config().forceIsoDateParsing && column?.serializeValue && lastIndex > 0) { + filter[lastIndex] = column.serializeValue(filter[lastIndex], 'filter'); + } + } else if (column?.selector) { + column.selector.columnIndex = column.index; + filter[0] = column.selector; + } + } }