diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts index 5788303cd1c7..102384180970 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts @@ -3,7 +3,11 @@ import { } from '@jest/globals'; import type { Properties as DataGridProperties } from '@js/ui/data_grid'; import errors from '@js/ui/widget/ui.errors'; +import { variableWrapper } from '@ts/core/utils/m_variable_wrapper'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; +import type { RawItemData } from '@ts/grids/grid_core/data_source_adapter/types'; import type { InternalGrid } from '@ts/grids/grid_core/m_types'; +import ko from 'knockout'; import type { DataGridInstance } from '../../__tests__/__mock__/helpers/utils'; import { @@ -75,6 +79,215 @@ describe('column fixing enablement', () => { }); }); +describe('column generation', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('should generate columns when an empty data source receives data', async () => { + const { instance } = await createDataGrid({ dataSource: [] }); + + expect(instance.columnCount()).toBe(0); + + instance.option('dataSource', [{ id: 1, name: 'a' }]); + jest.runAllTimers(); + + expect(instance.getVisibleColumns().map((column) => column.dataField)).toEqual(['id', 'name']); + }); +}); + +describe('observable column customization', () => { + beforeEach(() => { + beforeTest(); + variableWrapper.inject({ + isWrapped: ko.isObservable, + unwrap(value: unknown): unknown { + return ko.unwrap(value); + }, + }); + }); + afterEach(() => { + afterTest(); + variableWrapper.resetInjection(); + }); + + it('should invoke an observable callback without replacing its value', async () => { + const customizeColumns = (columns: Column[]): void => { + columns[0].caption = 'Customized'; + }; + const observable = ko.observable(customizeColumns); + const { instance } = await createDataGrid({ + dataSource: [{ id: 1 }], + columns: ['id'], + }); + + instance.option('customizeColumns', observable); + jest.runAllTimers(); + + expect(instance.columnOption('id', 'caption')).toBe('Customized'); + expect(observable()).toBe(customizeColumns); + }); +}); + +describe('fixed column layout', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('should preserve band colspan across fixed header rows without changing source columns', async () => { + const { instance } = await createDataGrid({ dataSource: [], columns: [] }); + const controller = instance.getController('columns'); + const left: Column = { + name: 'left', fixed: true, fixedPosition: 'left', headerId: 'left', + }; + const right: Column = { + name: 'right', fixed: true, fixedPosition: 'right', headerId: 'right', + }; + const rows: Column[][] = [ + [left, { name: 'band', isBand: true, colspan: 2 }, right], + [left, { name: 'first' }, { name: 'second' }, right], + ]; + jest.spyOn(controller, '_isColumnFixing').mockReturnValue(true); + jest.spyOn(controller, 'getRowCount').mockReturnValue(1); + jest.spyOn(controller, 'getVisibleColumns').mockImplementation((rowIndex) => rows[rowIndex ?? 1]); + controller.resetColumnsCache(); + + const fixedRows = [controller.getFixedColumns(0), controller.getFixedColumns(1)]; + + fixedRows.forEach((row) => { + expect(row).toEqual([ + { ...left, headerId: 'left-fixed' }, + { command: 'transparent', colspan: 2 }, + { ...right, headerId: 'right-fixed' }, + ]); + }); + expect(left.headerId).toBe('left'); + expect(right.headerId).toBe('right'); + expect(rows.map((row) => row.length)).toEqual([3, 4]); + }); +}); + +describe('getVisibleColumns', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('should keep null row indexes equivalent to omitted indexes', async () => { + const { instance } = await createDataGrid({ + dataSource: [{ id: 1, name: 'a' }], + columns: [{ caption: 'Band', columns: ['id', 'name'] }], + }); + + const visibleColumns = instance.getController('columns').getVisibleColumns(null); + + expect(visibleColumns).toEqual(instance.getVisibleColumns()); + expect(visibleColumns.map((column) => column.dataField)).toEqual(['id', 'name']); + expect(instance.getVisibleColumns(0).map((column) => column.caption)).toEqual(['Band']); + }); +}); + +describe('sort and group selectors', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('should skip empty sort and group selectors', async () => { + const { instance } = await createDataGrid({ + dataSource: [{ id: 1, name: 'a' }], + columns: [ + { dataField: 'id', sortOrder: 'asc', calculateSortValue: '' }, + { dataField: 'name', groupIndex: 0, calculateGroupValue: '' }, + ], + }); + + const controller = instance.getController('columns'); + + expect(controller.getSortDataSourceParameters()?.[0].selector).toBe('id'); + expect(controller.getGroupDataSourceParameters()?.[0].selector).toBe('name'); + }); +}); + +describe('column filter expressions', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('should forward filter target and preserve column callback context', async () => { + const calls: unknown[] = []; + const { instance } = await createDataGrid({ + dataSource: [{ id: 1 }, { id: 2 }], + columns: [{ + dataField: 'id', + calculateFilterExpression( + this: Column, + value: unknown, + operation: string | null, + target: string, + ): unknown[] { + calls.push({ + field: this.dataField, value, operation, target, + }); + return [this.dataField, operation, value]; + }, + }], + filterRow: { visible: true }, + }); + + instance.columnOption('id', 'selectedFilterOperation', '='); + instance.columnOption('id', 'filterValue', 2); + jest.runAllTimers(); + + expect(calls).toContainEqual({ + field: 'id', value: 2, operation: '=', target: 'filterRow', + }); + expect(instance.getVisibleRows()).toMatchObject([{ data: { id: 2 } }]); + }); +}); + +describe('column data type inference', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('should finish type inference before reading raw values for serialization', async () => { + const { instance } = await createDataGrid({ + dataSource: [], + columns: ['value'], + }); + const controller = instance.getController('columns'); + const column = controller.getColumns()[0]; + const calls: unknown[] = []; + const items = [{ value: 1 }, { value: '2' }]; + jest.spyOn(controller, '_getFirstItems').mockReturnValue(items); + column.calculateCellValue = function calculateCellValue( + this: Column, + item: RawItemData, + ...args: unknown[] + ): unknown { + calls.push({ + context: this, + value: item.value, + dataType: this.dataType, + skipDeserialization: args[0], + }); + return item.value; + }; + + const updated = controller.updateColumnDataTypes(); + + expect(updated).toBe(true); + expect(column.dataType).toBe('string'); + expect(calls).toEqual([ + { + context: column, value: 1, dataType: undefined, skipDeserialization: undefined, + }, + { + context: column, value: '2', dataType: undefined, skipDeserialization: undefined, + }, + { + context: column, value: 1, dataType: 'string', skipDeserialization: true, + }, + { + context: column, value: '2', dataType: 'string', skipDeserialization: true, + }, + ]); + }); +}); + describe('Bugs', () => { beforeEach(() => { beforeTest(); 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 43ea3b1c657c..94f2a60b4133 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 @@ -1,4 +1,3 @@ -/* eslint-disable prefer-destructuring */ 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'; @@ -34,7 +33,9 @@ import type { ColumnOptionsUpdate, ColumnsChanges, ColumnsControllerOptionChanged, + ColumnsControllerOptions, ColumnsDataSourceParameters, + ColumnSelector, ColumnsOptionChanged, ColumnUserState, DropLocationNames, @@ -152,7 +153,7 @@ export class ColumnsController extends modules.Controller { public _isWarnedAboutUnsupportedProperties?: boolean; - private getCommonColumnSettings(column?: Column): Partial { + private getCommonColumnSettings(column?: Column): Partial | undefined { switch (true) { case !column?.type: return this.option('commonColumnSettings'); @@ -184,7 +185,12 @@ export class ColumnsController extends modules.Controller { assignColumns(this, columns ? createColumnsFromOptions(this, columns) : []); this.applyUserState(); } else { - assignColumns(this, this._columnsUserState ? createColumnsFromOptions(this, this._columnsUserState) : this._columns); + assignColumns( + this, + this._columnsUserState + ? createColumnsFromOptions(this, this._columnsUserState) + : this._columns, + ); } addExpandColumn(this); @@ -214,20 +220,23 @@ export class ColumnsController extends modules.Controller { } public _getFirstItems(dataSourceAdapter?: DataSourceAdapter): RawItemData[] { - let groupsCount: number; let items: RawItemData[] = []; - const getFirstItemsCore = function ( - items: RawItemData[] | undefined, - groupsCount: number, - ): RawItemData[] | undefined { - if (!items || !groupsCount) { - return items; + const getFirstItemsCore = ( + groupItems: RawItemData[] | undefined, + remainingGroups: number, + ): RawItemData[] | undefined => { + if (!groupItems || !remainingGroups) { + return groupItems; } - for (let i = 0; i < items.length; i++) { - // @ts-expect-error the adapter does not describe nested group items - const childItems = getFirstItemsCore(items[i].items || items[i].collapsedItems, groupsCount - 1); - if (childItems && childItems.length) { + for (const item of groupItems) { + const childItems = getFirstItemsCore( + // @ts-expect-error the adapter does not describe nested group items + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- skip falsy + item.items || item.collapsedItems, + remainingGroups - 1, + ); + if (childItems?.length) { return childItems; } } @@ -235,14 +244,16 @@ export class ColumnsController extends modules.Controller { }; if (dataSourceAdapter && dataSourceAdapter.items().length > 0) { - groupsCount = gridCoreUtils.normalizeSortingInfo(dataSourceAdapter.group()).length; - items = getFirstItemsCore(dataSourceAdapter.items(), groupsCount) || []; + const groupsCount = gridCoreUtils.normalizeSortingInfo(dataSourceAdapter.group()).length; + items = getFirstItemsCore(dataSourceAdapter.items(), groupsCount) ?? []; } return items; } protected _endUpdateCore(): void { - !this._skipProcessingColumnsChange && fireColumnsChanged(this); + if (!this._skipProcessingColumnsChange) { + fireColumnsChanged(this); + } } protected callbackNames(): string[] { @@ -260,7 +271,7 @@ export class ColumnsController extends modules.Controller { path: string, columns?: (Column | string)[], ): Column | string | undefined { - const that = this; + // eslint-disable-next-line @typescript-eslint/init-declarations -- missing path let column: Column | string | undefined; const columnIndexes: number[] = []; @@ -273,13 +284,13 @@ export class ColumnsController extends modules.Controller { if (columnIndexes.length) { if (columns) { column = columnIndexes.reduce( - // @ts-expect-error option paths may contain string shorthand columns - // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- shorthand option paths - (column, index) => column && column.columns && column.columns[index], + (currentColumn, index) => currentColumn && ( + isString(currentColumn) ? undefined : currentColumn.columns?.[index] + ), { columns }, ); } else { - column = getColumnByIndexes(that, columnIndexes); + column = getColumnByIndexes(this, columnIndexes); } } @@ -287,19 +298,18 @@ export class ColumnsController extends modules.Controller { } public optionChanged(args: ColumnsControllerOptionChanged): void { - let needUpdateRequireResize; - switch (args.name) { case 'adaptColumnWidthByRatio': args.handled = true; break; case 'dataSource': - if (args.value !== args.previousValue && !this.option('columns') && (!Array.isArray(args.value) || !Array.isArray(args.previousValue))) { + if (args.value !== args.previousValue && !this.option('columns') + && (!Array.isArray(args.value) || !Array.isArray(args.previousValue))) { this._columns = []; } break; - case 'columns': - needUpdateRequireResize = this._skipProcessingColumnsChange; + case 'columns': { + let needUpdateRequireResize = this._skipProcessingColumnsChange; args.handled = true; if (!this._skipProcessingColumnsChange) { @@ -317,6 +327,7 @@ export class ColumnsController extends modules.Controller { this._updateRequireResize(args); } break; + } case 'commonColumnSettings': case 'columnAutoWidth': case 'allowColumnResizing': @@ -395,7 +406,10 @@ export class ColumnsController extends modules.Controller { } public publicMethods(): string[] { - return ['addColumn', 'deleteColumn', 'columnOption', 'columnCount', 'clearSorting', 'clearGrouping', 'getVisibleColumns', 'getVisibleColumnIndex', 'getColumns']; + return [ + 'addColumn', 'deleteColumn', 'columnOption', 'columnCount', 'clearSorting', 'clearGrouping', + 'getVisibleColumns', 'getVisibleColumnIndex', 'getColumns', + ]; } public applyDataSourceAdapter( @@ -403,28 +417,39 @@ export class ColumnsController extends modules.Controller { forceApplying?: boolean, isApplyingUserState?: boolean, ): DeferredObj | undefined { - const that = this; - const isDataSourceAdapterLoaded = dataSourceAdapter && dataSourceAdapter.isLoaded(); - - that.appliedDataSourceAdapter = dataSourceAdapter; - - if (!that.dataSourceAdapterApplied || that.generatedColumnsCount === 0 || forceApplying || that.option('regenerateColumnsByVisibleItems')) { - if (isDataSourceAdapterLoaded) { - if (!that._isColumnsFromOptions) { - const columnsFromDataSourceAdapter = createColumnsFromDataSourceAdapter(that, dataSourceAdapter); - if (columnsFromDataSourceAdapter.length) { - assignColumns(that, columnsFromDataSourceAdapter); - that.generatedColumnsCount = that._columns.length; - that.applyUserState(); - } + const isDataSourceAdapterLoaded = dataSourceAdapter?.isLoaded(); + + this.appliedDataSourceAdapter = dataSourceAdapter; + + const shouldApplyDataSourceAdapter = !this.dataSourceAdapterApplied + || this.generatedColumnsCount === 0 || Boolean(forceApplying) + || this.option('regenerateColumnsByVisibleItems'); + if (shouldApplyDataSourceAdapter) { + if (!dataSourceAdapter || !isDataSourceAdapterLoaded) { + this.dataSourceAdapterApplied = false; + updateIndexes(this); + return undefined; + } + + if (!this._isColumnsFromOptions) { + const columnsFromDataSourceAdapter = createColumnsFromDataSourceAdapter( + this, + dataSourceAdapter, + ); + if (columnsFromDataSourceAdapter.length) { + assignColumns(this, columnsFromDataSourceAdapter); + this.generatedColumnsCount = this._columns.length; + this.applyUserState(); } - return that.updateColumns(dataSourceAdapter, forceApplying, isApplyingUserState); } - that.dataSourceAdapterApplied = false; - updateIndexes(that); - } else if (isDataSourceAdapterLoaded && !that.isAllDataTypesDefined(true) && that.updateColumnDataTypes(dataSourceAdapter)) { - updateColumnChanges(that, 'columns'); - fireColumnsChanged(that); + return this.updateColumns(dataSourceAdapter, forceApplying, isApplyingUserState); + } + + const hasUpdatedDataTypes = dataSourceAdapter && isDataSourceAdapterLoaded + && !this.isAllDataTypesDefined(true) && this.updateColumnDataTypes(dataSourceAdapter); + if (hasUpdatedDataTypes) { + updateColumnChanges(this, 'columns'); + fireColumnsChanged(this); // @ts-expect-error promise() is typed as native Promise but exposes Deferred callbacks return Deferred().reject().promise(); } @@ -443,15 +468,15 @@ export class ColumnsController extends modules.Controller { * @private */ public resetColumnsCache(): void { - const that = this; - that._visibleColumns = undefined; - that._fixedColumns = undefined; - that._rowCount = undefined; - resetBandColumnsCache(that); + this._visibleColumns = undefined; + this._fixedColumns = undefined; + this._rowCount = undefined; + resetBandColumnsCache(this); } public reinit(ignoreColumnOptionNames?: string[] | false): void { this._columnsUserState = this.getUserState(); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- false clears list this._ignoreColumnOptionNames = ignoreColumnOptionNames || null; this.init(); @@ -476,14 +501,16 @@ export class ColumnsController extends modules.Controller { // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- extend has an untyped result return extend({ allowFixing: this.option('columnFixing.enabled'), - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- false -> undefined allowResizing: this.option('allowColumnResizing') || undefined, allowReordering: this.option('allowColumnReordering'), minWidth: this.option('columnMinWidth'), width: this.option('columnWidth'), autoExpandGroup: groupingOptions.autoExpandAll, allowCollapsing: groupingOptions.allowCollapsing, - allowGrouping: groupPanelOptions.allowColumnDragging && groupPanelOptions.visible || groupingOptions.contextMenuEnabled, + allowGrouping: (groupPanelOptions.allowColumnDragging && groupPanelOptions.visible) + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- false fallback + || groupingOptions.contextMenuEnabled, }, commonColumnSettings); } @@ -504,11 +531,14 @@ export class ColumnsController extends modules.Controller { return false; } - for (let i = 0; i < columns.length; i++) { - if (!columns[i].dataField && columns[i].calculateCellValue === columns[i].defaultCalculateCellValue) { - continue; - } - if (!columns[i].dataType || (checkSerializers && columns[i].deserializeValue && columns[i].serializationFormat === undefined)) { + for (const column of columns) { + const requiresDataType = Boolean(column.dataField) + || column.calculateCellValue !== column.defaultCalculateCellValue; + const isDataTypeIncomplete = requiresDataType && (!column.dataType + || (checkSerializers && column.deserializeValue + && column.serializationFormat === undefined)); + + if (isDataTypeIncomplete) { return false; } } @@ -531,11 +561,11 @@ export class ColumnsController extends modules.Controller { public getGroupColumns(): GroupColumn[] { const result: GroupColumn[] = []; - this._columns.forEach((column) => { - if (isDefined(column.groupIndex) && !column.type) { - result[column.groupIndex] = column as GroupColumn; - } - }); + this._columns + .filter((column): column is GroupColumn => isDefined(column.groupIndex) && !column.type) + .forEach((column) => { + result[column.groupIndex] = column; + }); return result; } @@ -549,31 +579,30 @@ export class ColumnsController extends modules.Controller { /** * @extended: virtual_column */ + protected _compileVisibleColumns(rowIndex?: number | null, isBase?: boolean): Column[]; protected _compileVisibleColumns(rowIndex?: number | null): Column[] { - this._visibleColumns = this._visibleColumns || this._compileVisibleColumnsCore(); - rowIndex = isDefined(rowIndex) ? rowIndex : this._visibleColumns.length - 1; + this._visibleColumns = this._visibleColumns ?? this._compileVisibleColumnsCore(); + const effectiveRowIndex = isDefined(rowIndex) ? rowIndex : this._visibleColumns.length - 1; - return this._visibleColumns[rowIndex] || []; + return this._visibleColumns[effectiveRowIndex] ?? []; } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - public getVisibleColumns(rowIndex?: number | null, isBase?: boolean): Column[] { + public getVisibleColumns(...args: [rowIndex?: number | null, isBase?: boolean]): Column[] { if (!this._shouldReturnVisibleColumns()) { return []; } - // @ts-expect-error extenders forward IArguments rather than a typed tuple - return this._compileVisibleColumns.apply(this, arguments); + return this._compileVisibleColumns(...args); } /** * @extended: virtual_column */ public getFixedColumns(rowIndex?: number | null): Column[] { - this._fixedColumns = this._fixedColumns || this._getFixedColumnsCore(); - rowIndex = isDefined(rowIndex) ? rowIndex : this._fixedColumns.length - 1; + this._fixedColumns = this._fixedColumns ?? this._getFixedColumnsCore(); + const effectiveRowIndex = isDefined(rowIndex) ? rowIndex : this._fixedColumns.length - 1; - return this._fixedColumns[rowIndex] || []; + return this._fixedColumns[effectiveRowIndex] ?? []; } public getFilteringColumns(): FilterField[] { @@ -605,7 +634,7 @@ export class ColumnsController extends modules.Controller { return 0; } - // TODO: Need to rename this method to getFixedColumns after removing old fixed columns implementation + // TODO: Rename to getFixedColumns after removing the old fixed columns implementation public getStickyColumns(rowIndex?: number): Column[] { const visibleColumns = this.getVisibleColumns(rowIndex, true); @@ -613,63 +642,56 @@ export class ColumnsController extends modules.Controller { } private _getFixedColumnsCore(): Column[][] { - const that = this; const result: Column[][] = []; - const rowCount = that.getRowCount(); - const isColumnFixing = that._isColumnFixing(); - const transparentColumn = { command: 'transparent' } as Column; + const rowCount = this.getRowCount(); + const isColumnFixing = this._isColumnFixing(); + if (!isColumnFixing) { + return result; + } + const transparentColumn: Column = { command: 'transparent' }; let transparentColspan = 0; - let notFixedColumnCount; - let transparentColumnIndex: number | null; - let lastFixedPosition: Column['fixedPosition'] | null; - - if (isColumnFixing) { - for (let i = 0; i <= rowCount; i++) { - notFixedColumnCount = 0; - lastFixedPosition = null; - transparentColumnIndex = null; - const visibleColumns = that.getVisibleColumns(i, true); - - for (let j = 0; j < visibleColumns.length; j++) { - const prevColumn = visibleColumns[j - 1]; - const column = visibleColumns[j]; - - if (!column.fixed || column.fixedPosition === StickyPosition.Sticky) { - if (i === 0) { - if (column.isBand && column.colspan) { - transparentColspan += column.colspan; - } else { - transparentColspan++; - } - } + const getColspan = (column: Column): number => ( + column.isBand && column.colspan ? column.colspan : 1 + ); - notFixedColumnCount++; - if (!isDefined(transparentColumnIndex)) { - transparentColumnIndex = j; - } - } else if (prevColumn && prevColumn.fixed && getFixedPosition(that, prevColumn) !== getFixedPosition(that, column)) { - if (!isDefined(transparentColumnIndex)) { - transparentColumnIndex = j; - } - } else { - lastFixedPosition = column.fixedPosition; - } - } + for (let i = 0; i <= rowCount; i += 1) { + let notFixedColumnCount = 0; + let lastFixedPosition: Column['fixedPosition'] | null = null; + let transparentColumnIndex: number | null = null; + const visibleColumns = this.getVisibleColumns(i, true); - if (i === 0 && (notFixedColumnCount === 0 || notFixedColumnCount >= visibleColumns.length)) { - return []; - } + for (let j = 0; j < visibleColumns.length; j += 1) { + const prevColumn = visibleColumns[j - 1]; + const column = visibleColumns[j]; - if (!isDefined(transparentColumnIndex)) { - transparentColumnIndex = lastFixedPosition === 'right' ? 0 : visibleColumns.length; - } + if (!column.fixed || column.fixedPosition === StickyPosition.Sticky) { + transparentColspan += i === 0 ? getColspan(column) : 0; - result[i] = visibleColumns.slice(0); - if (!transparentColumn.colspan) { - transparentColumn.colspan = transparentColspan; + notFixedColumnCount += 1; + transparentColumnIndex ??= j; + } else if (prevColumn && prevColumn.fixed + && getFixedPosition(this, prevColumn) !== getFixedPosition(this, column)) { + transparentColumnIndex ??= j; + } else { + lastFixedPosition = column.fixedPosition; } - result[i].splice(transparentColumnIndex, notFixedColumnCount, transparentColumn); } + + if (i === 0 + && (notFixedColumnCount === 0 || notFixedColumnCount >= visibleColumns.length)) { + return []; + } + + if (!isDefined(transparentColumnIndex)) { + transparentColumnIndex = lastFixedPosition === 'right' ? 0 : visibleColumns.length; + } + + result[i] = visibleColumns.slice(0); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- replace zero + if (!transparentColumn.colspan) { + transparentColumn.colspan = transparentColspan; + } + result[i].splice(transparentColumnIndex, notFixedColumnCount, transparentColumn); } return result.map((columns) => columns.map((column) => { @@ -700,15 +722,11 @@ export class ColumnsController extends modules.Controller { private getExpandColumns(): Column[] { let expandColumns: Column[] = this._getExpandColumnsCore(); - let expandColumn: Column | undefined; const firstGroupColumn = expandColumns.filter((column) => column.groupIndex === 0)[0]; const isFixedFirstGroupColumn = firstGroupColumn && firstGroupColumn.fixed; const isColumnFixing = this._isColumnFixing(); const rtlEnabled = this.option('rtlEnabled'); - - if (expandColumns.length) { - expandColumn = this.columnOption('command:expand'); - } + const expandColumn = expandColumns.length ? this.columnOption('command:expand') : undefined; // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- extend has an untyped result expandColumns = map(expandColumns, (column: Column): Column => extend( @@ -728,6 +746,7 @@ export class ColumnsController extends modules.Controller { expandColumn, { index: column.index, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- skip empty type type: column.type || GROUP_COMMAND_COLUMN_NAME, }, )); @@ -848,58 +867,60 @@ export class ColumnsController extends modules.Controller { const isVisible = this._isColumnVisible(column); const isInGroupPanel = this._isColumnInGroupPanel(column); - if (isVisible && !isInGroupPanel) { - const parentBandColumns = getParentBandColumns( - column.index, - bandColumnsCache.columnParentByIndex, - ); - const rowIndex = parentBandColumns.length; - let targetIndex: string | number = visibleIndex ?? 'undefined'; - // eslint-disable-next-line @typescript-eslint/init-declarations - let indexedColumns: Record; - - if (isDefined(visibleIndex) && visibleIndex < 0) { - targetIndex = -visibleIndex; - indexedColumns = negativeIndexedColumns[rowIndex]; - } else { - column.fixed = parentBandColumns[0]?.fixed ?? column.fixed; - column.fixedPosition = parentBandColumns[0]?.fixedPosition ?? column.fixedPosition; + if (!isVisible || isInGroupPanel) { + return; + } - if (column.fixed && column.fixedPosition !== StickyPosition.Sticky) { - const isDefaultCommandColumn = !!column.command - && !gridCoreUtils.isCustomCommandColumn(this._columns, column); + const parentBandColumns = getParentBandColumns( + column.index, + bandColumnsCache.columnParentByIndex, + ); + const rowIndex = parentBandColumns.length; + let targetIndex: string | number = visibleIndex ?? 'undefined'; + // eslint-disable-next-line @typescript-eslint/init-declarations + let indexedColumns: Record; + + if (isDefined(visibleIndex) && visibleIndex < 0) { + targetIndex = -visibleIndex; + indexedColumns = negativeIndexedColumns[rowIndex]; + } else { + column.fixed = parentBandColumns[0]?.fixed ?? column.fixed; + column.fixedPosition = parentBandColumns[0]?.fixedPosition ?? column.fixedPosition; - let isFixedToEnd = column.fixedPosition === 'right'; + if (column.fixed && column.fixedPosition !== StickyPosition.Sticky) { + const isDefaultCommandColumn = !!column.command + && !gridCoreUtils.isCustomCommandColumn(this._columns, column); - if (rtlEnabled && !isDefaultCommandColumn) { - isFixedToEnd = !isFixedToEnd; - } + let isFixedToEnd = column.fixedPosition === 'right'; - indexedColumns = isFixedToEnd - ? positiveIndexedColumns[rowIndex][2] - : positiveIndexedColumns[rowIndex][0]; - } else { - indexedColumns = positiveIndexedColumns[rowIndex][1]; + if (rtlEnabled && !isDefaultCommandColumn) { + isFixedToEnd = !isFixedToEnd; } + + indexedColumns = isFixedToEnd + ? positiveIndexedColumns[rowIndex][2] + : positiveIndexedColumns[rowIndex][0]; + } else { + [, indexedColumns] = positiveIndexedColumns[rowIndex]; } + } - // normalizeIndexes gives every band and band child a numeric visibleIndex - if (parentBandColumns.length && isNumeric(targetIndex)) { - targetIndex = numberToString(targetIndex, columnDigitsCount); + // normalizeIndexes gives every band and band child a numeric visibleIndex + if (parentBandColumns.length && isNumeric(targetIndex)) { + targetIndex = numberToString(targetIndex, columnDigitsCount); - for (let i = parentBandColumns.length - 1; i >= 0; i -= 1) { - const { visibleIndex: parentVisibleIndex } = parentBandColumns[i]; + for (let i = parentBandColumns.length - 1; i >= 0; i -= 1) { + const { visibleIndex: parentVisibleIndex } = parentBandColumns[i]; - if (isNumeric(parentVisibleIndex)) { - const parentTargetIndex = numberToString(parentVisibleIndex, columnDigitsCount); - targetIndex = `${parentTargetIndex}${targetIndex}`; - } + if (isNumeric(parentVisibleIndex)) { + const parentTargetIndex = numberToString(parentVisibleIndex, columnDigitsCount); + targetIndex = `${parentTargetIndex}${targetIndex}`; } } - - indexedColumns[targetIndex] = indexedColumns[targetIndex] || []; - indexedColumns[targetIndex].push(column); } + + indexedColumns[targetIndex] = indexedColumns[targetIndex] || []; + indexedColumns[targetIndex].push(column); }); return { @@ -960,22 +981,20 @@ export class ColumnsController extends modules.Controller { columns?: Column[], bandColumnIndex?: number, ): Column[] { - const that = this; let result: Column[] = []; - let hiddenColumnsByBand: Column[]; + const sourceColumns = columns ?? this._columns; - columns = columns || that._columns; - - each(columns, (_, column) => { + sourceColumns.forEach((column) => { if (column.ownerBand !== bandColumnIndex) { return; } if (column.isBand) { - if (!column.visible) { - hiddenColumnsByBand = that.getChildrenByBandColumn(column.index); - } else { - hiddenColumnsByBand = that.getInvisibleColumns(that.getChildrenByBandColumn(column.index), column.index); - } + const hiddenColumnsByBand = !column.visible + ? this.getChildrenByBandColumn(column.index) + : this.getInvisibleColumns( + this.getChildrenByBandColumn(column.index), + column.index, + ); if (hiddenColumnsByBand.length) { result.push(column); @@ -1011,23 +1030,29 @@ export class ColumnsController extends modules.Controller { sourceLocation: DropLocationNames, targetLocation: DropLocationNames, ): boolean | undefined { - const that = this; - const columnIndex = getColumnIndexByVisibleIndex(that, fromVisibleIndex, sourceLocation); - const sourceColumn = that._columns[columnIndex]; + const columnIndex = getColumnIndexByVisibleIndex(this, fromVisibleIndex, sourceLocation); + const sourceColumn = this._columns[columnIndex]; - if (sourceColumn && (sourceColumn.allowReordering || sourceColumn.allowGrouping || sourceColumn.allowHiding)) { + const allowsMoving = sourceColumn + && (Boolean(sourceColumn.allowReordering) + || Boolean(sourceColumn.allowGrouping) || Boolean(sourceColumn.allowHiding)); + if (allowsMoving) { if (sourceLocation === targetLocation) { if (sourceLocation === COLUMN_CHOOSER_LOCATION) { return false; } - fromVisibleIndex = isObject(fromVisibleIndex) ? fromVisibleIndex.columnIndex : fromVisibleIndex; - toVisibleIndex = isObject(toVisibleIndex) ? toVisibleIndex.columnIndex : toVisibleIndex; + const fromColumnIndex = isObject(fromVisibleIndex) + ? fromVisibleIndex.columnIndex : fromVisibleIndex; + const toColumnIndex = isObject(toVisibleIndex) + ? toVisibleIndex.columnIndex : toVisibleIndex; - return fromVisibleIndex !== toVisibleIndex && fromVisibleIndex + 1 !== toVisibleIndex; - } if ((sourceLocation === GROUP_LOCATION && targetLocation !== COLUMN_CHOOSER_LOCATION) || targetLocation === GROUP_LOCATION) { + return fromColumnIndex !== toColumnIndex && fromColumnIndex + 1 !== toColumnIndex; + } if ((sourceLocation === GROUP_LOCATION && targetLocation !== COLUMN_CHOOSER_LOCATION) + || targetLocation === GROUP_LOCATION) { return sourceColumn && sourceColumn.allowGrouping; - } if (sourceLocation === COLUMN_CHOOSER_LOCATION || targetLocation === COLUMN_CHOOSER_LOCATION) { + } if (sourceLocation === COLUMN_CHOOSER_LOCATION + || targetLocation === COLUMN_CHOOSER_LOCATION) { return sourceColumn && sourceColumn.allowHiding; } return true; @@ -1041,40 +1066,40 @@ export class ColumnsController extends modules.Controller { sourceLocation: DropLocationNames, targetLocation: DropLocationNames, ): void { - const that = this; const options: Partial = {}; + // eslint-disable-next-line @typescript-eslint/init-declarations -- absent groupIndex let prevGroupIndex: number | undefined; - const fromIndex = getColumnIndexByVisibleIndex(that, fromVisibleIndex, sourceLocation); - const toIndex = getColumnIndexByVisibleIndex(that, toVisibleIndex, targetLocation); - let targetGroupIndex; + const fromIndex = getColumnIndexByVisibleIndex(this, fromVisibleIndex, sourceLocation); + const toIndex = getColumnIndexByVisibleIndex(this, toVisibleIndex, targetLocation); if (fromIndex >= 0) { - const column = that._columns[fromIndex]; - toVisibleIndex = isObject(toVisibleIndex) ? toVisibleIndex.columnIndex : toVisibleIndex; - targetGroupIndex = toIndex >= 0 ? that._columns[toIndex].groupIndex : -1; + const column = this._columns[fromIndex]; + const toColumnIndex = isObject(toVisibleIndex) + ? toVisibleIndex.columnIndex : toVisibleIndex; + let targetGroupIndex = toIndex >= 0 ? this._columns[toIndex].groupIndex : -1; if (isDefined(column.groupIndex) && sourceLocation === GROUP_LOCATION) { - if (targetGroupIndex > column.groupIndex) { - targetGroupIndex--; + if (isDefined(targetGroupIndex) && targetGroupIndex > column.groupIndex) { + targetGroupIndex -= 1; } if (targetLocation !== GROUP_LOCATION) { options.groupIndex = undefined; } else { prevGroupIndex = column.groupIndex; delete column.groupIndex; - updateColumnGroupIndexes(that); + updateColumnGroupIndexes(this); } } if (targetLocation === GROUP_LOCATION) { - options.groupIndex = reserveGroupIndex(that, targetGroupIndex); + options.groupIndex = reserveGroupIndex(this, targetGroupIndex); column.groupIndex = prevGroupIndex; - } else if (toVisibleIndex >= 0) { - const targetColumn = that._columns[toIndex]; + } else if (toColumnIndex >= 0) { + const targetColumn = this._columns[toIndex]; if (!targetColumn || column.ownerBand !== targetColumn.ownerBand) { options.visibleIndex = MAX_SAFE_INTEGER; - } else if (isColumnFixed(that, column) !== isColumnFixed(that, targetColumn)) { + } else if (isColumnFixed(this, column) !== isColumnFixed(this, targetColumn)) { options.visibleIndex = MAX_SAFE_INTEGER; } else { options.visibleIndex = targetColumn.visibleIndex; @@ -1087,7 +1112,7 @@ export class ColumnsController extends modules.Controller { options.visible = isVisible; } - that.columnOption(column.index, options); + this.columnOption(column.index, options); } } @@ -1099,22 +1124,21 @@ export class ColumnsController extends modules.Controller { } public changeSortOrder(columnIndex: number, sortOrder?: string | null): void { - const that = this; const options: Partial = {}; - const sortingOptions = that.option('sorting'); + const sortingOptions = this.option('sorting'); const sortingMode = sortingOptions?.mode; const needResetSorting = sortingMode === 'single' || !sortOrder; - const column = that._columns[columnIndex]; - const nextSortOrder = function (column: Column): boolean { + const column = this._columns[columnIndex]; + const nextSortOrder = (sortingColumn: Column): boolean => { if (sortOrder === 'ctrl') { - if (!(('sortOrder' in column) && ('sortIndex' in column))) { + if (!(('sortOrder' in sortingColumn) && ('sortIndex' in sortingColumn))) { return false; } options.sortOrder = undefined; options.sortIndex = undefined; - } else if (isDefined(column.groupIndex) || isDefined(column.sortIndex)) { - options.sortOrder = column.sortOrder === 'desc' ? 'asc' : 'desc'; + } else if (isDefined(sortingColumn.groupIndex) || isDefined(sortingColumn.sortIndex)) { + options.sortOrder = sortingColumn.sortOrder === 'desc' ? 'asc' : 'desc'; } else { options.sortOrder = 'asc'; } @@ -1124,12 +1148,12 @@ export class ColumnsController extends modules.Controller { if (this.allowColumnSorting(column)) { if (needResetSorting && !isDefined(column.groupIndex)) { - each(that._columns, function (this: Column, index: number) { - if (index !== columnIndex && this.sortOrder) { - if (!isDefined(this.groupIndex)) { - delete this.sortOrder; + this._columns.forEach((currentColumn, index) => { + if (index !== columnIndex && currentColumn.sortOrder) { + if (!isDefined(currentColumn.groupIndex)) { + delete currentColumn.sortOrder; } - delete this.sortIndex; + delete currentColumn.sortIndex; } }); } @@ -1147,7 +1171,7 @@ export class ColumnsController extends modules.Controller { } } - that.columnOption(column.index, options); + this.columnOption(column.index, options); } /** @@ -1160,25 +1184,30 @@ export class ColumnsController extends modules.Controller { public getSortDataSourceParameters( useLocalSelector?: boolean, ): ColumnDataSourceParameter[] | null { - const that = this; const sortColumns: Column[] = []; const sort: ColumnDataSourceParameter[] = []; - each(that._columns, function (this: Column) { - if ((this.dataField || this.selector || this.calculateCellValue) && isDefined(this.sortIndex) && !isDefined(this.groupIndex)) { - sortColumns[this.sortIndex] = this; + this._columns.forEach((column) => { + const hasSortSelector = Boolean(column.dataField) + || Boolean(column.selector) || Boolean(column.calculateCellValue); + if (hasSortSelector && isDefined(column.sortIndex) && !isDefined(column.groupIndex)) { + sortColumns[column.sortIndex] = column; } }); - each(sortColumns, function (this: Column) { - const sortOrder = this && this.sortOrder; + sortColumns.forEach((column) => { + const { sortOrder } = column; if (isSortOrderValid(sortOrder)) { const sortItem: ColumnDataSourceParameter = { - selector: this.calculateSortValue || this.displayField || this.calculateDisplayValue || (useLocalSelector && this.selector) || this.dataField || this.calculateCellValue, - desc: this.sortOrder === 'desc', + /* eslint-disable @typescript-eslint/prefer-nullish-coalescing -- skip falsy selectors */ + selector: column.calculateSortValue || column.displayField || column.calculateDisplayValue + || (useLocalSelector && column.selector) + || column.dataField || column.calculateCellValue, + /* eslint-enable @typescript-eslint/prefer-nullish-coalescing */ + desc: column.sortOrder === 'desc', }; - if (this.sortingMethod) { - sortItem.compare = this.sortingMethod.bind(this); + if (column.sortingMethod) { + sortItem.compare = column.sortingMethod.bind(column); } sort.push(sortItem); @@ -1192,32 +1221,36 @@ export class ColumnsController extends modules.Controller { ): ColumnDataSourceParameter[] | null { const group: ColumnDataSourceParameter[] = []; - each(this.getGroupColumns(), function (this: GroupColumn) { - const selector = this.calculateGroupValue || this.displayField || this.calculateDisplayValue || (useLocalSelector && this.selector) || this.dataField || this.calculateCellValue; + for (const column of this.getGroupColumns()) { + /* eslint-disable @typescript-eslint/prefer-nullish-coalescing -- skip falsy selectors */ + const selector = column.calculateGroupValue || column.displayField + || column.calculateDisplayValue + || (useLocalSelector && column.selector) || column.dataField || column.calculateCellValue; + /* eslint-enable @typescript-eslint/prefer-nullish-coalescing */ if (selector) { const groupItem: ColumnDataSourceParameter = { selector, - desc: this.sortOrder === 'desc', - isExpanded: !!this.autoExpandGroup, + desc: column.sortOrder === 'desc', + isExpanded: !!column.autoExpandGroup, }; - if (this.sortingMethod) { - groupItem.compare = this.sortingMethod.bind(this); + if (column.sortingMethod) { + groupItem.compare = column.sortingMethod.bind(column); } group.push(groupItem); } - }); + } return group.length > 0 ? group : null; } public refresh(updateNewLookupsOnly?: boolean): DeferredObj { const deferreds: (DeferredObj | undefined)[] = []; - each(this._columns, function (this: Column) { - const { lookup } = this; + this._columns.forEach((column) => { + const { lookup } = column; - if (lookup && !this.calculateDisplayValue) { + if (lookup && !column.calculateDisplayValue) { if (updateNewLookupsOnly && lookup.valueMap) { return; } @@ -1244,13 +1277,15 @@ export class ColumnsController extends modules.Controller { ? this._previousColumns?.[columnIndex].selector?.originalCallback ?? column.selector : column.selector; - each(['calculateSortValue', 'calculateGroupValue', 'calculateDisplayValue'], (_, calculateCallbackName) => { + [ + 'calculateSortValue', 'calculateGroupValue', 'calculateDisplayValue', + ].forEach((calculateCallbackName) => { const calculateCallback = column[calculateCallbackName]; if (isFunction(calculateCallback)) { if (!calculateCallback.originalCallback) { const context = { column }; - column[calculateCallbackName] = function (data): unknown { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- column callback is untyped + column[calculateCallbackName] = function calculateValue(data): unknown { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- untyped callback return calculateCallback.call(context.column, data); }; column[calculateCallbackName].originalCallback = calculateCallback; @@ -1264,11 +1299,10 @@ export class ColumnsController extends modules.Controller { if (isString(column.calculateDisplayValue)) { column.displayField = column.calculateDisplayValue; - // @ts-expect-error compileGetter does not describe the returned getter - column.calculateDisplayValue = compileGetter(column.displayField); + column.calculateDisplayValue = compileGetter(column.displayField) as ColumnSelector; } if (column.calculateDisplayValue) { - column.displayValueMap = column.displayValueMap || {}; + column.displayValueMap = column.displayValueMap ?? {}; } updateSerializers(column, column.dataType); @@ -1280,110 +1314,130 @@ export class ColumnsController extends modules.Controller { const dataType = lookup ? lookup.dataType : column.dataType; if (dataType) { - column.alignment = column.alignment || getAlignmentByDataType(dataType, this.option('rtlEnabled')); + column.alignment = column.alignment + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- skip empty text + || getAlignmentByDataType(dataType, this.option('rtlEnabled')); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- skip falsy format column.format = column.format || gridCoreUtils.getFormatByDataType(dataType); - column.customizeText = column.customizeText || getCustomizeTextByDataType(dataType); - column.defaultFilterOperations = column.defaultFilterOperations || !lookup && DATATYPE_OPERATIONS[dataType] || []; + column.customizeText = column.customizeText ?? getCustomizeTextByDataType(dataType); + column.defaultFilterOperations = column.defaultFilterOperations + ?? (lookup ? [] : (DATATYPE_OPERATIONS[dataType] ?? [])); if (!isDefined(column.filterOperations)) { setFilterOperationsAsDefaultValues(column); } - column.defaultFilterOperation = column.filterOperations && column.filterOperations[0] || '='; - column.showEditorAlways = isDefined(column.showEditorAlways) ? column.showEditorAlways : dataType === 'boolean' && !column.cellTemplate && !column.lookup; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- skip empty string + column.defaultFilterOperation = column.filterOperations?.[0] || '='; + column.showEditorAlways = isDefined(column.showEditorAlways) + ? column.showEditorAlways : dataType === 'boolean' && !column.cellTemplate && !column.lookup; } } public updateColumnDataTypes(dataSourceAdapter?: DataSourceAdapter): boolean { - const that = this; - const dateSerializationFormat = that.option('dateSerializationFormat'); - const firstItems = that._getFirstItems(dataSourceAdapter); + const dateSerializationFormat = this.option('dateSerializationFormat'); + const firstItems = this._getFirstItems(dataSourceAdapter); let isColumnDataTypesUpdated = false; - each(that._columns, (index, column) => { + each(this._columns, (index, column) => { if (column.type === AI_COLUMN_NAME) { return; } - let i; - let value; - let dataType; - let lookupDataType; - let valueDataType; + // eslint-disable-next-line @typescript-eslint/init-declarations -- absent samples + let dataType: Column['dataType']; + // eslint-disable-next-line @typescript-eslint/init-declarations -- absent lookup samples + let lookupDataType: Column['dataType']; const { lookup } = column; if (gridCoreUtils.isDateType(column.dataType) && column.serializationFormat === undefined) { column.serializationFormat = dateSerializationFormat; } - if (lookup && gridCoreUtils.isDateType(lookup.dataType) && column.serializationFormat === undefined) { + if (lookup && gridCoreUtils.isDateType(lookup.dataType) + && column.serializationFormat === undefined) { lookup.serializationFormat = dateSerializationFormat; } - if (column.calculateCellValue && firstItems.length) { - if (!column.dataType || (lookup && !lookup.dataType)) { - for (i = 0; i < firstItems.length; i++) { - value = column.calculateCellValue(firstItems[i]); - - if (!column.dataType) { - valueDataType = getValueDataType(value); - dataType = dataType || valueDataType; - if (dataType && valueDataType && dataType !== valueDataType) { - dataType = 'string'; - } - } + const inferDataTypes = (): void => { + for (const item of firstItems) { + const value = column.calculateCellValue(item); - if (lookup && !lookup.dataType) { - valueDataType = getValueDataType(gridCoreUtils.getDisplayValue(column, value, firstItems[i])); - lookupDataType = lookupDataType || valueDataType; - if (lookupDataType && valueDataType && lookupDataType !== valueDataType) { - lookupDataType = 'string'; - } + if (!column.dataType) { + const valueDataType = getValueDataType(value); + dataType = dataType ?? valueDataType; + if (dataType && valueDataType && dataType !== valueDataType) { + dataType = 'string'; } } - if (dataType || lookupDataType) { - if (dataType) { - column.dataType = dataType; - } - if (lookup && lookupDataType) { - lookup.dataType = lookupDataType; + if (lookup && !lookup.dataType) { + const valueDataType = getValueDataType( + gridCoreUtils.getDisplayValue(column, value, item), + ); + lookupDataType = lookupDataType ?? valueDataType; + if (lookupDataType && valueDataType && lookupDataType !== valueDataType) { + lookupDataType = 'string'; } - isColumnDataTypesUpdated = true; } } - if (column.serializationFormat === undefined || (lookup && lookup.serializationFormat === undefined)) { - for (i = 0; i < firstItems.length; i++) { - value = column.calculateCellValue(firstItems[i], true); + if (dataType || lookupDataType) { + if (dataType) { + column.dataType = dataType; + } - if (column.serializationFormat === undefined) { - column.serializationFormat = getSerializationFormat(column.dataType, value); - } + if (lookup && lookupDataType) { + lookup.dataType = lookupDataType; + } + isColumnDataTypesUpdated = true; + } + }; - if (lookup && lookup.serializationFormat === undefined) { - lookup.serializationFormat = getSerializationFormat(lookup.dataType, lookup.calculateCellValue(value, true)); - } + const inferSerializationFormats = (): void => { + for (const item of firstItems) { + const value = column.calculateCellValue(item, true); + + if (column.serializationFormat === undefined) { + column.serializationFormat = getSerializationFormat(column.dataType, value); + } + + if (lookup && lookup.serializationFormat === undefined) { + lookup.serializationFormat = getSerializationFormat( + lookup.dataType, + lookup.calculateCellValue(value, true), + ); } } + }; + + if (column.calculateCellValue && firstItems.length) { + if (!column.dataType || (lookup && !lookup.dataType)) { + inferDataTypes(); + } + const needsSerializationFormat = column.serializationFormat === undefined + || (lookup && lookup.serializationFormat === undefined); + if (needsSerializationFormat) { + inferSerializationFormats(); + } } - that._updateColumnOptions(column, index); + this._updateColumnOptions(column, index); }); return isColumnDataTypesUpdated; } private _customizeColumns(columns: Column[]): void { - const that = this; - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- module option (never-typed) - const customizeColumns: any = that.option('customizeColumns'); + const customizeColumns = ( + this.option('customizeColumns') as ColumnsControllerOptions['customizeColumns'] + ); if (customizeColumns) { const hasOwnerBand = columns.some((column) => isObject(column.ownerBand)); if (hasOwnerBand) { - updateIndexes(that); + updateIndexes(this); } customizeColumns(columns); - assignColumns(that, createColumnsFromOptions(that, columns)); + assignColumns(this, createColumnsFromOptions(this, columns)); } } @@ -1436,128 +1490,155 @@ export class ColumnsController extends modules.Controller { this.dataSourceAdapterApplied = true; } - if (!gridCoreUtils.equalSortParameters(parameters.sorting, this.getSortDataSourceParameters())) { + if (!gridCoreUtils.equalSortParameters( + parameters.sorting, + this.getSortDataSourceParameters(), + )) { updateColumnChanges(this, 'sorting'); } - if (!gridCoreUtils.equalSortParameters(parameters.grouping, this.getGroupDataSourceParameters())) { + if (!gridCoreUtils.equalSortParameters( + parameters.grouping, + this.getGroupDataSourceParameters(), + )) { updateColumnChanges(this, 'grouping'); } - updateColumnChanges(this, 'columns'); - - this._columnChanges!.appliedFilters ??= []; - this._columnChanges!.appliedFilters.push(parameters.filtering); + const columnChanges = updateColumnChanges(this, 'columns'); + columnChanges.appliedFilters ??= []; + columnChanges.appliedFilters.push(parameters.filtering); } public updateSortingGrouping( dataSourceAdapter?: DataSourceAdapter | null, fromDataSource?: boolean, ): void { - // eslint-disable-next-line @typescript-eslint/no-this-alias - const that = this; - // eslint-disable-next-line @typescript-eslint/init-declarations - let isColumnsChanged: boolean | undefined; - // eslint-disable-next-line func-names - const updateSortGroupParameterIndexes = function ( + if (!dataSourceAdapter) { + return; + } + + let isColumnsChanged = false; + const updateSortGroupParameterIndexes = ( columns: Column[], sortParameters: SortingInfo[] | null | undefined, indexParameterName: string, - ): void { + ): void => { const referencedGroupValues: string[] = columns .filter((column): column is Column & { calculateGroupValue: string } => ( isString(column.calculateGroupValue) )) .map((column) => column.calculateGroupValue); - each(columns, (_: number, column) => { + columns.forEach((column) => { const isReferencedAsGroupValue = indexParameterName === 'groupIndex' && referencedGroupValues.some( (groupValue) => column.dataField === groupValue || column.name === groupValue, ); - if (!isReferencedAsGroupValue) { + if (isReferencedAsGroupValue) { + return; + } // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete column[indexParameterName]; - if (sortParameters) { - for (let i = 0; i < sortParameters.length; i += 1) { - const { selector, isExpanded } = sortParameters[i]; - - if (selector === column.dataField - || selector === column.name - || selector === column.displayField - || gridCoreUtils.isEqualSelectors(selector, column.selector) - || gridCoreUtils.isSelectorEqualWithCallback(selector, column.calculateCellValue) - || gridCoreUtils.isEqualSelectors(selector, column.calculateGroupValue) - || gridCoreUtils.isSelectorEqualWithCallback(selector, column.calculateDisplayValue) - ) { - if (fromDataSource) { - column.sortOrder = 'sortOrder' in column ? column.sortOrder : sortParameters[i].desc ? 'desc' : 'asc'; - } else { - column.sortOrder = column.sortOrder ?? (sortParameters[i].desc ? 'desc' : 'asc'); - } - - if (isExpanded !== undefined) { - column.autoExpandGroup = isExpanded; - } - - column[indexParameterName] = i; - break; - } - } + delete column[indexParameterName]; + if (!sortParameters) { + return; + } + const parameterIndex = sortParameters.findIndex(({ selector }) => ( + selector === column.dataField + || selector === column.name + || selector === column.displayField + || gridCoreUtils.isEqualSelectors(selector, column.selector) + || gridCoreUtils.isSelectorEqualWithCallback(selector, column.calculateCellValue) + || gridCoreUtils.isEqualSelectors(selector, column.calculateGroupValue) + || (isFunction(column.calculateDisplayValue) + && gridCoreUtils.isSelectorEqualWithCallback(selector, column.calculateDisplayValue)) + )); + if (parameterIndex < 0) { + return; + } + + const { desc, isExpanded } = sortParameters[parameterIndex]; + if (fromDataSource) { + let { sortOrder } = column; + if (!('sortOrder' in column)) { + sortOrder = desc ? 'desc' : 'asc'; } + column.sortOrder = sortOrder; + } else { + column.sortOrder = column.sortOrder ?? (desc ? 'desc' : 'asc'); } + + if (isExpanded !== undefined) { + column.autoExpandGroup = isExpanded; + } + + column[indexParameterName] = parameterIndex; }); }; - if (dataSourceAdapter) { - const sortParameters = gridCoreUtils.normalizeSortingInfo(dataSourceAdapter.sort()); - const groupParameters = gridCoreUtils.normalizeSortingInfo(dataSourceAdapter.group()); - const columnsGroupParameters = that.getGroupDataSourceParameters(); - const columnsSortParameters = that.getSortDataSourceParameters(); - const changeTypes = this._columnChanges?.changeTypes; - const sortingChanged = !gridCoreUtils.equalSortParameters(sortParameters, columnsSortParameters); - const needToApplySortingFromDataSource = fromDataSource && !changeTypes?.sorting; - const needToApplyGroupingFromDataSource = fromDataSource && !changeTypes?.grouping; - const groupingChanged = !gridCoreUtils.equalSortParameters(groupParameters, columnsGroupParameters, true); - const groupExpandingChanged = !groupingChanged && !gridCoreUtils.equalSortParameters(groupParameters, columnsGroupParameters); - - if (!that._columns.length) { - each(groupParameters, (_: number, group) => { - that._columns.push(group.selector); - }); - each(sortParameters, (_: number, sort) => { - if (!isFunction(sort.selector)) { - that._columns.push(sort.selector); - } - }); - assignColumns(that, createColumnsFromOptions(that, that._columns)); - } + const sortParameters = gridCoreUtils.normalizeSortingInfo(dataSourceAdapter.sort()); + const groupParameters = gridCoreUtils.normalizeSortingInfo(dataSourceAdapter.group()); + const columnsGroupParameters = this.getGroupDataSourceParameters(); + const columnsSortParameters = this.getSortDataSourceParameters(); + const changeTypes = this._columnChanges?.changeTypes; + const sortingChanged = !gridCoreUtils.equalSortParameters( + sortParameters, + columnsSortParameters, + ); + const needToApplySortingFromDataSource = fromDataSource && !changeTypes?.sorting; + const needToApplyGroupingFromDataSource = fromDataSource && !changeTypes?.grouping; + const groupingChanged = !gridCoreUtils.equalSortParameters( + groupParameters, + columnsGroupParameters, + true, + ); + const groupExpandingChanged = !groupingChanged + && !gridCoreUtils.equalSortParameters(groupParameters, columnsGroupParameters); - if ((needToApplyGroupingFromDataSource || (!columnsGroupParameters && !that._hasUserState)) && (groupingChanged || groupExpandingChanged)) { - /// #DEBUG - that.__groupingUpdated = true; - /// #ENDDEBUG - updateSortGroupParameterIndexes(that._columns, groupParameters, 'groupIndex'); - if (fromDataSource) { - groupingChanged && updateColumnChanges(that, 'grouping'); - groupExpandingChanged && updateColumnChanges(that, 'groupExpanding'); - isColumnsChanged = true; + if (!this._columns.length) { + each(groupParameters, (_: number, group) => { + this._columns.push(group.selector); + }); + each(sortParameters, (_: number, sort) => { + if (!isFunction(sort.selector)) { + this._columns.push(sort.selector); } - } - - if ((needToApplySortingFromDataSource || (!columnsSortParameters && !that._hasUserState)) && sortingChanged) { - /// #DEBUG - that.__sortingUpdated = true; - /// #ENDDEBUG - updateSortGroupParameterIndexes(that._columns, sortParameters, 'sortIndex'); - if (fromDataSource) { - updateColumnChanges(that, 'sorting'); - isColumnsChanged = true; + }); + assignColumns(this, createColumnsFromOptions(this, this._columns)); + } + + const shouldApplyGrouping = (Boolean(needToApplyGroupingFromDataSource) + || (!columnsGroupParameters && !this._hasUserState)) + && (groupingChanged || groupExpandingChanged); + if (shouldApplyGrouping) { + /// #DEBUG + this.__groupingUpdated = true; + /// #ENDDEBUG + updateSortGroupParameterIndexes(this._columns, groupParameters, 'groupIndex'); + if (fromDataSource) { + if (groupingChanged) { + updateColumnChanges(this, 'grouping'); } + if (groupExpandingChanged) { + updateColumnChanges(this, 'groupExpanding'); + } + isColumnsChanged = true; } - if (isColumnsChanged) { - fireColumnsChanged(that); + } + + const shouldApplySorting = (Boolean(needToApplySortingFromDataSource) + || (!columnsSortParameters && !this._hasUserState)) && sortingChanged; + if (shouldApplySorting) { + /// #DEBUG + this.__sortingUpdated = true; + /// #ENDDEBUG + updateSortGroupParameterIndexes(this._columns, sortParameters, 'sortIndex'); + if (fromDataSource) { + updateColumnChanges(this, 'sorting'); + isColumnsChanged = true; } } + if (isColumnsChanged) { + fireColumnsChanged(this); + } } public columnCount(): number { @@ -1589,8 +1670,7 @@ export class ColumnsController extends modules.Controller { value?: unknown, notFireEvent?: boolean, ): unknown { - const that = this; - const columns = that._columns.concat(that._commandColumns); + const columns = this._columns.concat(this._commandColumns); const column = findColumn(columns, identifier); if (!column) { @@ -1602,39 +1682,39 @@ export class ColumnsController extends modules.Controller { } if (isString(option) && arguments.length === 2) { - return columnOptionCore(that, column, option); + return columnOptionCore(this, column, option); } const applyOptions = (): void => { if (isString(option)) { - columnOptionCore(that, column, option, value, notFireEvent); + columnOptionCore(this, column, option, value, notFireEvent); } else if (isObject(option)) { each(option, (optionName, optionValue) => { - columnOptionCore(that, column, optionName, optionValue, notFireEvent); + columnOptionCore(this, column, optionName, optionValue, notFireEvent); }); } - fireColumnsChanged(that); + fireColumnsChanged(this); }; - const isWidthChanging = that._isWidthChanging(column, option, value, notFireEvent); + const isWidthChanging = this._isWidthChanging(column, option, value, notFireEvent); const needOwnUpdateCycle = isWidthChanging - && !that._updateLockCount - && !that.component._updateLockCount; + && !this._updateLockCount + && !this.component._updateLockCount; if (needOwnUpdateCycle) { - that.component.beginUpdate(); + this.component.beginUpdate(); try { applyOptions(); - that.setRequireResize(); + this.setRequireResize(); } finally { - that.component.endUpdate(); + this.component.endUpdate(); } } else { applyOptions(); if (isWidthChanging) { - that.setRequireResize(); + this.setRequireResize(); } } @@ -1642,35 +1722,33 @@ export class ColumnsController extends modules.Controller { } private clearSorting(): void { - const that = this; const columnCount = this.columnCount(); - that.beginUpdate(); + this.beginUpdate(); - for (let i = 0; i < columnCount; i++) { - that.columnOption(i, 'sortOrder', undefined); - // option needs to be deleted from column to prevert conflicts in syncing loadOptions from dataSource. See T1147379 - delete (findColumn(that._columns, i) as Column).sortOrder; + for (let i = 0; i < columnCount; i += 1) { + this.columnOption(i, 'sortOrder', undefined); + // Delete the option to prevent loadOptions synchronization conflicts (T1147379). + delete (findColumn(this._columns, i) as Column).sortOrder; } - that.endUpdate(); + this.endUpdate(); } public clearGrouping(): void { - const that = this; const columnCount = this.columnCount(); - that.beginUpdate(); + this.beginUpdate(); - for (let i = 0; i < columnCount; i++) { - that.columnOption(i, 'groupIndex', undefined); + for (let i = 0; i < columnCount; i += 1) { + this.columnOption(i, 'groupIndex', undefined); } - that.endUpdate(); + this.endUpdate(); } public getVisibleIndex(index: number | undefined, rowIndex?: number | null): number { const columns = this.getVisibleColumns(rowIndex); - for (let i = columns.length - 1; i >= 0; i--) { + for (let i = columns.length - 1; i >= 0; i -= 1) { if (columns[i].index === index) { return i; } @@ -1680,8 +1758,10 @@ export class ColumnsController extends modules.Controller { public getVisibleIndexByColumn(column: Column, rowIndex?: number | null): number { const visibleColumns = this.getVisibleColumns(rowIndex); - const visibleColumn = visibleColumns.filter((col) => col.index === column.index && col.command === column.command)[0]; - return visibleColumns.indexOf(visibleColumn); + const visibleColumn = visibleColumns.find((col) => ( + col.index === column.index && col.command === column.command + )); + return visibleColumn ? visibleColumns.indexOf(visibleColumn) : -1; } public getVisibleColumnIndex(id: ColumnIdentifier, rowIndex?: number | null): number { @@ -1691,43 +1771,46 @@ export class ColumnsController extends modules.Controller { } private addColumn(options: Column | string): void { - const that = this; - let column = createColumn(that, options) as Column; - const index = that._columns.length; + let column = createColumn(this, options) as Column; + const index = this._columns.length; - that._columns.push(column); + this._columns.push(column); if (column.isBand) { - that._columns = createColumnsFromOptions(that, that._columns); - column = that._columns[index]; + this._columns = createColumnsFromOptions(this, this._columns); + column = this._columns[index]; } column.added = options; - updateIndexes(that, column); - that.updateColumns(that.appliedDataSourceAdapter); - that._checkColumns(); + updateIndexes(this, column); + this.updateColumns(this.appliedDataSourceAdapter); + this._checkColumns(); } private deleteColumn(id: ColumnIdentifier): void { - const that = this; - const column = that.columnOption(id); + const column = this.columnOption(id); if (column && isDefined(column.index) && column.index >= 0) { - convertOwnerBandToColumnReference(that._columns); - that._columns.splice(column.index, 1); + convertOwnerBandToColumnReference(this._columns); + this._columns.splice(column.index, 1); if (column.isBand) { - const childIndexes = that.getChildrenByBandColumn(column.index).map((column) => column.index); - that._columns = that._columns.filter((column) => !childIndexes.includes(column.index)); + const childIndexes = this.getChildrenByBandColumn(column.index) + .map((childColumn) => childColumn.index); + this._columns = this._columns.filter((currentColumn) => ( + !childIndexes.includes(currentColumn.index) + )); } - updateIndexes(that); - that.updateColumns(that.appliedDataSourceAdapter); + updateIndexes(this); + this.updateColumns(this.appliedDataSourceAdapter); } } public addCommandColumn(options: Column): void { - let commandColumn = this._commandColumns.filter((column) => column.command === options.command)[0]; + let commandColumn = this._commandColumns.find((column) => ( + column.command === options.command + )); if (!commandColumn) { commandColumn = options; @@ -1745,9 +1828,12 @@ export class ColumnsController extends modules.Controller { const { columns, hasAddedBands } = new UserStateApplier({ columns: this._columns, columnsUserState, - ignoreColumnOptionNames: this._ignoreColumnOptionNames || [], + ignoreColumnOptionNames: this._ignoreColumnOptionNames ?? [], hasUserState: this._hasUserState, - createColumn: (columnOptions): Column => createColumn(this, columnOptions) as Column, + createColumn: (columnOptions): Column => createColumn( + this, + isString(columnOptions) ? { dataField: columnOptions } : columnOptions, + ), }).apply(); if (hasAddedBands) { @@ -1761,23 +1847,24 @@ export class ColumnsController extends modules.Controller { private getUserState(): ColumnUserState[] { const columns = this._columns; const result: ColumnUserState[] = []; - let i; + let i = 0; - function handleStateField(index: number, value: keyof ColumnUserState): void { + function handleStateField(value: keyof ColumnUserState): void { if (columns[i][value] !== undefined) { result[i][value] = columns[i][value]; } } - for (i = 0; i < columns.length; i++) { + for (i = 0; i < columns.length; i += 1) { result[i] = {}; - each(USER_STATE_FIELD_NAMES, handleStateField); + USER_STATE_FIELD_NAMES.forEach(handleStateField); } return result; } public setName(column: Column): void { if (!isColumnNameRequired(column)) { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- skip empty names column.name = column.name || column.dataField || column.type; } } @@ -1849,7 +1936,10 @@ export class ColumnsController extends modules.Controller { this._columns.forEach((column) => { const { name } = column; const isBand = column.columns?.length; - const isEditable = column.allowEditing && (column.dataField || column.setCellValue) && !isBand; + + const isEditable = column.allowEditing + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- skip empty field + && (column.dataField || column.setCellValue) && !isBand; if (name) { if (usedNames[name]) { @@ -1882,16 +1972,18 @@ export class ColumnsController extends modules.Controller { bandColumn?: Column, ): Column { let calculatedColumnOptions: Column = {}; - let dataField: Column['dataField'] | null = columnOptions.dataField; + let { dataField }: { dataField?: Column['dataField'] | null } = columnOptions; - if (Array.isArray(columnOptions.columns) && columnOptions.columns.length || columnOptions.isBand) { + const isBand = (Array.isArray(columnOptions.columns) && columnOptions.columns.length) + || columnOptions.isBand; + if (isBand) { calculatedColumnOptions.isBand = true; dataField = null; } if (dataField) { if (isString(dataField)) { - const getter = compileGetter(dataField); + const getter = compileGetter(dataField) as ColumnSelector; calculatedColumnOptions = { caption: inflector.captionize(dataField), calculateCellValue( @@ -1899,30 +1991,29 @@ export class ColumnsController extends modules.Controller { data: RawItemData, skipDeserialization?: boolean, ): unknown { - // @ts-expect-error compileGetter does not describe the returned getter const value = getter(data); - return this.deserializeValue && !skipDeserialization ? this.deserializeValue(value) : value; + return this.deserializeValue && !skipDeserialization + ? this.deserializeValue(value) : value; }, setCellValue: defaultSetCellValue, parseValue(text: string): unknown { - const column = this; + // eslint-disable-next-line @typescript-eslint/init-declarations -- invalid input let result: unknown; - let parsedValue; - if (column.dataType === 'number') { - if (isString(text) && column.format) { - result = strictParseNumber(text.trim(), column.format); + if (this.dataType === 'number') { + if (isString(text) && this.format) { + result = strictParseNumber(text.trim(), this.format); } else if (isDefined(text) && isNumeric(text)) { result = Number(text); } - } else if (column.dataType === 'boolean') { - if (text === column.trueText) { + } else if (this.dataType === 'boolean') { + if (text === this.trueText) { result = true; - } else if (text === column.falseText) { + } else if (text === this.falseText) { result = false; } - } else if (gridCoreUtils.isDateType(column.dataType)) { - parsedValue = dateLocalization.parse(text, column.format); + } else if (gridCoreUtils.isDateType(this.dataType)) { + const parsedValue = dateLocalization.parse(text, this.format); if (parsedValue) { result = parsedValue; } @@ -1938,24 +2029,26 @@ export class ColumnsController extends modules.Controller { } else { calculatedColumnOptions.allowFiltering = !!columnOptions.calculateFilterExpression; } - calculatedColumnOptions.calculateFilterExpression = function (): - ReturnType> { - // @ts-expect-error filterUtils is untyped - const expression = filterUtils.defaultCalculateFilterExpression.apply(this, arguments); + calculatedColumnOptions.calculateFilterExpression = function calculateFilterExpression( + ...args: Parameters> + ): ReturnType> { + const expression = filterUtils.defaultCalculateFilterExpression.call(this, ...args); // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- filterUtils is untyped return expression; }; calculatedColumnOptions.defaultFilterOperation = '='; - calculatedColumnOptions.createFilterExpression = function ( + calculatedColumnOptions.createFilterExpression = function createFilterExpression( this: Column, filterValue: unknown, selectedFilterOperation: string | null | undefined, ): DataFilter { + // eslint-disable-next-line @typescript-eslint/init-declarations -- missing callback let result: ColumnFilterExpression | null | undefined; if (this.calculateFilterExpression) { // @ts-expect-error filter callbacks have a partially typed variadic contract + // eslint-disable-next-line prefer-spread, prefer-rest-params -- forward filter arguments result = this.calculateFilterExpression.apply(this, arguments); } if (isFunction(result)) { @@ -1971,13 +2064,9 @@ export class ColumnsController extends modules.Controller { }; if (!dataField || !isString(dataField)) { - extend(true, calculatedColumnOptions, { - allowSorting: false, - allowGrouping: false, - calculateCellValue(): null { - return null; - }, - }); + calculatedColumnOptions.allowSorting = false; + calculatedColumnOptions.allowGrouping = false; + calculatedColumnOptions.calculateCellValue = (): null => null; } if (bandColumn) { @@ -1986,17 +2075,22 @@ export class ColumnsController extends modules.Controller { if (columnOptions.dataType) { calculatedColumnOptions.userDataType = columnOptions.dataType; } - if (columnOptions.selectedFilterOperation && !('defaultSelectedFilterOperation' in calculatedColumnOptions)) { - calculatedColumnOptions.defaultSelectedFilterOperation = columnOptions.selectedFilterOperation; + if (columnOptions.selectedFilterOperation + && !('defaultSelectedFilterOperation' in calculatedColumnOptions)) { + calculatedColumnOptions.defaultSelectedFilterOperation = ( + columnOptions.selectedFilterOperation + ); } if (columnOptions.lookup && columnOptions.type !== AI_COLUMN_NAME) { calculatedColumnOptions.lookup = { calculateCellValue(value: unknown, skipDeserialization?: boolean): unknown { + let lookupValue = value; if (this.valueExpr) { // @ts-expect-error lookup values are supplied by an untyped data source - value = this.valueMap && this.valueMap[value]; + lookupValue = this.valueMap?.[value]; } - return this.deserializeValue && !skipDeserialization ? this.deserializeValue(value) : value; + return this.deserializeValue && !skipDeserialization + ? this.deserializeValue(lookupValue) : lookupValue; }, updateValueMap(): void { @@ -2006,38 +2100,39 @@ export class ColumnsController extends modules.Controller { const calculateValue: any = compileGetter(this.valueExpr); // eslint-disable-next-line @typescript-eslint/no-explicit-any -- compileGetter result const calculateDisplayValue: any = compileGetter(this.displayExpr); - for (let i = 0; i < this.items.length; i++) { - const item = this.items[i]; + for (const item of this.items) { const displayValue = calculateDisplayValue(item); this.valueMap[calculateValue(item)] = displayValue; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- empty type this.dataType = this.dataType || getValueDataType(displayValue); } } }, update(): DeferredObj | undefined { - const that = this; - let { dataSource } = that; + let { dataSource } = this; if (dataSource) { if (isFunction(dataSource) && !variableWrapper.isWrapped(dataSource)) { dataSource = dataSource({}); } - if (isPlainObject(dataSource) || (dataSource instanceof Store) || Array.isArray(dataSource)) { - if (that.valueExpr) { + const isSupportedDataSource = isPlainObject(dataSource) + || (dataSource instanceof Store) || Array.isArray(dataSource); + if (isSupportedDataSource) { + if (this.valueExpr) { const dataSourceOptions = normalizeDataSourceOptions(dataSource); dataSourceOptions.paginate = false; dataSource = new DataSource(dataSourceOptions); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- DataSource is untyped + // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- untyped source return dataSource.load().done((data) => { - that.items = data; - that.updateValueMap && that.updateValueMap(); + this.items = data; + this.updateValueMap?.(); }); } } else { errors.log('E1016'); } } else { - that.updateValueMap && that.updateValueMap(); + this.updateValueMap?.(); } return undefined; }, @@ -2049,7 +2144,7 @@ export class ColumnsController extends modules.Controller { calculatedColumnOptions.resizedCallbacks.add(columnOptions.resized.bind(columnOptions)); } - each(calculatedColumnOptions, (optionName: string) => { + Object.keys(calculatedColumnOptions).forEach((optionName) => { if (isFunction(calculatedColumnOptions[optionName]) && !optionName.startsWith('default')) { const defaultOptionName = `default${optionName.charAt(0).toUpperCase()}${optionName.substr(1)}`; calculatedColumnOptions[defaultOptionName] = calculatedColumnOptions[optionName]; @@ -2060,6 +2155,7 @@ export class ColumnsController extends modules.Controller { } public getRowCount(): number { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- recompute zero this._rowCount = this._rowCount || getRowCount(this); return this._rowCount; @@ -2071,6 +2167,7 @@ export class ColumnsController extends modules.Controller { return 0; } + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- skip empty command const isCommandOrGroupColumn = column.command || this._isColumnInGroupPanel(column); const isVisibleDataColumn = column.visible && !isCommandOrGroupColumn; if (!alwaysGetRowIndex && !isVisibleDataColumn) { @@ -2085,15 +2182,18 @@ export class ColumnsController extends modules.Controller { bandColumnIndex: number | undefined, onlyVisibleDirectChildren?: boolean, ): Column[] { - const that = this; - const bandColumnsCache = that.getBandColumnsCache(); - const result = getChildrenByBandColumn(bandColumnIndex, bandColumnsCache.columnChildrenByIndex, !onlyVisibleDirectChildren); + const bandColumnsCache = this.getBandColumnsCache(); + const result = getChildrenByBandColumn( + bandColumnIndex, + bandColumnsCache.columnChildrenByIndex, + !onlyVisibleDirectChildren, + ); if (onlyVisibleDirectChildren) { return result .filter((column) => column.visible && !column.command) .sort((column1, column2) => ( - (column1.visibleIndex as number) - (column2.visibleIndex as number) + Number(column1.visibleIndex) - Number(column2.visibleIndex) )); } @@ -2111,16 +2211,16 @@ export class ColumnsController extends modules.Controller { let result = false; const column = this._columns[columnIndex]; const bandColumnsCache = this.getBandColumnsCache(); - const parentBandColumns = column && getParentBandColumns(columnIndex, bandColumnsCache.columnParentByIndex); + const parentBandColumns = column + && getParentBandColumns(columnIndex, bandColumnsCache.columnParentByIndex); if (parentBandColumns) { // T416483 - fix for jquery 2.1.4 - each(parentBandColumns, (_, bandColumn): boolean | undefined => { + for (const bandColumn of parentBandColumns) { if (bandColumn.index === bandColumnIndex) { result = true; - return false; + break; } - return undefined; - }); + } } return result; @@ -2129,12 +2229,17 @@ export class ColumnsController extends modules.Controller { public isParentColumnVisible(columnIndex: number | undefined): boolean { let result = true; const bandColumnsCache = this.getBandColumnsCache(); - const bandColumns = columnIndex !== undefined && columnIndex >= 0 && getParentBandColumns(columnIndex, bandColumnsCache.columnParentByIndex); - - bandColumns && each(bandColumns, (_, bandColumn) => { - result = result && !!bandColumn.visible; - return result; - }); + const bandColumns = columnIndex !== undefined && columnIndex >= 0 + && getParentBandColumns(columnIndex, bandColumnsCache.columnParentByIndex); + + if (bandColumns) { + for (const bandColumn of bandColumns) { + result = result && !!bandColumn.visible; + if (!result) { + break; + } + } + } return result; } diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts index 116806807628..aaf321a128fd 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts @@ -69,12 +69,24 @@ export const setFilterOperationsAsDefaultValues = (column: Column): void => { let globalColumnId = 1; -export const createColumn = ( +export function createColumn( + that: ColumnsController, + columnOptions: Column, + userStateColumnOptions?: ColumnUserState, + bandColumn?: Column, +): Column; +export function createColumn( that: ColumnsController, columnOptions: Column | string | undefined, userStateColumnOptions?: ColumnUserState, bandColumn?: Column, -): Column | undefined => { +): Column | undefined; +export function createColumn( + that: ColumnsController, + columnOptions: Column | string | undefined, + userStateColumnOptions?: ColumnUserState, + bandColumn?: Column, +): Column | undefined { if (!columnOptions) { return undefined; } @@ -114,7 +126,7 @@ export const createColumn = ( } return result; -}; +} export function isUserStateColumn( column: ColumnUserState | undefined, @@ -665,12 +677,12 @@ export function assignColumns(that: ColumnsController, columns: Column[]): void that.updateColumnDataTypes(); } -export const updateColumnChanges = ( +export function updateColumnChanges( that: ColumnsController, changeType: ColumnChangeType, optionName?: string, columnIndex?: number, -): void => { +): ColumnsChanges { const columnChanges: ColumnsChanges = that._columnChanges ?? { optionNames: { length: 0 }, changeTypes: { length: 0 }, @@ -707,7 +719,8 @@ export const updateColumnChanges = ( that._columnChanges = columnChanges; that.resetColumnsCache(); -}; + return columnChanges; +} export const fireColumnsChanged = (that: ColumnsController): void => { const { onColumnsChanging } = that.option() as ColumnsControllerOptions;