From 04d47e848e1441aa5fb234b17fad41431fb5e8af Mon Sep 17 00:00:00 2001 From: Aliar666 Date: Tue, 27 Oct 2026 00:04:12 +0400 Subject: [PATCH 01/10] Grids: type column chooser view fields --- .../column_chooser/m_column_chooser.ts | 99 ++++++++++++------- 1 file changed, 61 insertions(+), 38 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts index ba9d34c4cf57..b07650040aee 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts @@ -1,5 +1,6 @@ /* eslint-disable max-classes-per-file */ import messageLocalization from '@js/common/core/localization/message'; +import type { Coordinates as Offset } from '@js/core/renderer'; import $ from '@js/core/renderer'; import { deferUpdate } from '@js/core/utils/common'; import type { DeferredObj } from '@js/core/utils/deferred'; @@ -15,6 +16,7 @@ import type { Item } from '@js/ui/tree_view'; import TreeView from '@js/ui/tree_view'; import type { DataChange } from '@ts/grids/grid_core/data_controller/types'; import type { RowsView } from '@ts/grids/grid_core/views/rows_view'; +import type { BoundingRect } from '@ts/grids/grid_core/views/types'; import type { ColumnHeadersView } from '../column_headers/m_column_headers'; import type { ColumnsController } from '../columns_controller/columns_controller'; @@ -126,13 +128,13 @@ export class ColumnChooserController extends modules.ViewController { } export class ColumnChooserView extends ColumnsView { - private _popupContainer: any; + private _popupContainer?: Popup; - private _columnChooserList: any; + private _columnChooserList?: TreeView; - private _isUpdatingColumnVisibility: any; + private _isUpdatingColumnVisibility?: boolean; - private _isPopupContainerShown: any; + private _isPopupContainerShown?: boolean; public optionChanged(args) { switch (args.name) { @@ -154,12 +156,11 @@ export class ColumnChooserView extends ColumnsView { } - private _initializePopupContainer() { - const that = this; - const columnChooserClass = that.addWidgetPrefix(COLUMN_CHOOSER_CLASS); + private _initializePopupContainer(): Popup { + const columnChooserClass = this.addWidgetPrefix(COLUMN_CHOOSER_CLASS); // @ts-expect-error the view is rendered here - const $element = that.element().addClass(columnChooserClass); - const columnChooserOptions = that.option('columnChooser')!; + const $element = this.element().addClass(columnChooserClass); + const columnChooserOptions = this.option('columnChooser')!; const popupPosition = this._columnChooserController.getPosition(); const dxPopupOptions = { @@ -175,18 +176,18 @@ export class ColumnChooserView extends ColumnsView { position: popupPosition, width: columnChooserOptions.width, height: columnChooserOptions.height, - rtlEnabled: that.option('rtlEnabled'), + rtlEnabled: this.option('rtlEnabled'), container: columnChooserOptions.container, tabFocusLoopEnabled: true, _ignoreCloseOnChildEscape: true, } as PopupProperties; if (!isDefined(this._popupContainer)) { - that._popupContainer = that._createComponent($element, Popup, dxPopupOptions); + this._popupContainer = this._createComponent($element, Popup, dxPopupOptions); - that._popupContainer.on('optionChanged', (args) => { + this._popupContainer.on('optionChanged', (args) => { if (args.name === 'visible') { - that.renderCompleted.fire(); + this.renderCompleted.fire(); } }); } else { @@ -194,25 +195,33 @@ export class ColumnChooserView extends ColumnsView { } this.setPopupAttributes(); + + return this._popupContainer; } - private setPopupAttributes() { + private setPopupAttributes(): void { + const popupContainer = this._popupContainer; + + if (!popupContainer) { + return; + } + const isSelectMode = this.isSelectMode(); const isBandColumnsUsed = this._columnsController.isBandColumnsUsed(); - this._popupContainer.setAria({ + popupContainer.setAria({ role: 'dialog', label: messageLocalization.format('dxDataGrid-columnChooserTitle'), }); - this._popupContainer.$wrapper() - .toggleClass(this.addWidgetPrefix(COLUMN_CHOOSER_DRAG_CLASS), !isSelectMode) + popupContainer.$wrapper() + ?.toggleClass(this.addWidgetPrefix(COLUMN_CHOOSER_DRAG_CLASS), !isSelectMode) .toggleClass(this.addWidgetPrefix(COLUMN_CHOOSER_SELECT_CLASS), isSelectMode); - this._popupContainer.$content().addClass(this.addWidgetPrefix(COLUMN_CHOOSER_LIST_CLASS)); + popupContainer.$content()?.addClass(this.addWidgetPrefix(COLUMN_CHOOSER_LIST_CLASS)); if (isSelectMode && !isBandColumnsUsed) { - this._popupContainer.$content().addClass(this.addWidgetPrefix(COLUMN_CHOOSER_PLAIN_CLASS)); + popupContainer.$content()?.addClass(this.addWidgetPrefix(COLUMN_CHOOSER_PLAIN_CLASS)); } } @@ -232,7 +241,11 @@ export class ColumnChooserView extends ColumnsView { private _renderTreeView() { const that = this; - const $container = this._popupContainer.$content(); + const $container = this._popupContainer?.$content(); + + if (!$container) { + return; + } const columnChooser = this.option('columnChooser')!; const isSelectMode = this.isSelectMode(); @@ -370,26 +383,32 @@ export class ColumnChooserView extends ColumnsView { }; } - private _updateItems() { + private _updateItems(): void { const isSelectMode = this.isSelectMode(); const chooserColumns = this._columnsController.getChooserColumns(isSelectMode); const items = processItems(this, chooserColumns); - this._columnChooserList.option('items', items); + this._columnChooserList?.option('items', items); } private _updateItemsSelection(columnIndices) { + const columnChooserList = this._columnChooserList; + + if (!columnChooserList) { + return; + } + const changedColumns = columnIndices?.map((columnIndex) => this._columnsController.columnOption(columnIndex)); - this._columnChooserList.beginUpdate(); + columnChooserList.beginUpdate(); changedColumns?.forEach(({ visible, index }) => { if (visible) { - this._columnChooserList.selectItem(index); + columnChooserList.selectItem(index); } else { - this._columnChooserList.unselectItem(index); + columnChooserList.unselectItem(index); } }); - this._columnChooserList.endUpdate(); + columnChooserList.endUpdate(); } protected _columnOptionChanged(changes): void { @@ -467,38 +486,42 @@ export class ColumnChooserView extends ColumnsView { return this.isColumnChooserVisible() && isParentColumnVisible && isColumnHidden; } - public getBoundingRect() { - const that = this; - const container = that._popupContainer?.$overlayContent(); + public getBoundingRect(): BoundingRect | null { + const container = this._popupContainer?.$overlayContent(); if (container?.is(':visible')) { - const offset = container.offset(); + // @ts-expect-error offset() is typed as possibly undefined + const offset: Offset = container.offset(); return { left: offset.left, top: offset.top, - right: offset.left + getOuterWidth(container), - bottom: offset.top + getOuterHeight(container), + right: offset.left + Number(getOuterWidth(container)), + bottom: offset.top + Number(getOuterHeight(container)), }; } return null; } - public showColumnChooser() { + public showColumnChooser(): void { /// #DEBUG this._isPopupContainerShown = true; /// #ENDDEBUG - if (!this._popupContainer) { - this._initializePopupContainer(); + let popupContainer = this._popupContainer; + + if (!popupContainer) { + popupContainer = this._initializePopupContainer(); this.render(); } - this._popupContainer.show(); + // eslint-disable-next-line @typescript-eslint/no-floating-promises -- fire-and-forget + popupContainer.show(); } - private hideColumnChooser() { + private hideColumnChooser(): void { if (this._popupContainer) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises -- fire-and-forget this._popupContainer.hide(); /// #DEBUG @@ -507,7 +530,7 @@ export class ColumnChooserView extends ColumnsView { } } - public isColumnChooserVisible() { + public isColumnChooserVisible(): boolean | undefined { const popupContainer = this._popupContainer; return popupContainer?.option('visible'); From 1f8afcbee7b01c97187bce9d7365ef1e937590f1 Mon Sep 17 00:00:00 2001 From: Aliar666 Date: Tue, 27 Oct 2026 00:15:56 +0400 Subject: [PATCH 02/10] Grids: add column chooser types --- .../column_chooser/m_column_chooser.ts | 20 ++++++++----------- .../grids/grid_core/column_chooser/types.ts | 19 ++++++++++++++++++ 2 files changed, 27 insertions(+), 12 deletions(-) create mode 100644 packages/devextreme/js/__internal/grids/grid_core/column_chooser/types.ts diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts index b07650040aee..e8ec835e8ec8 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts @@ -12,7 +12,6 @@ import { isDefined } from '@js/core/utils/type'; import Button from '@js/ui/button'; import type { Properties as PopupProperties } from '@js/ui/popup'; import Popup from '@js/ui/popup/ui.popup'; -import type { Item } from '@js/ui/tree_view'; import TreeView from '@js/ui/tree_view'; import type { DataChange } from '@ts/grids/grid_core/data_controller/types'; import type { RowsView } from '@ts/grids/grid_core/views/rows_view'; @@ -20,12 +19,13 @@ import type { BoundingRect } from '@ts/grids/grid_core/views/types'; import type { ColumnHeadersView } from '../column_headers/m_column_headers'; import type { ColumnsController } from '../columns_controller/columns_controller'; -import type { DropLocationNames } from '../columns_controller/types'; +import type { Column, DropLocationNames } from '../columns_controller/types'; import type { HeaderPanel } from '../header_panel/header_panel'; import modules from '../modules/modules'; import type { ModuleType } from '../types'; import { ColumnsView } from '../views/columns_view'; import { defaultOptions } from './const'; +import type { ColumnChooserItem, NodeInternal } from './types'; const COLUMN_CHOOSER_CLASS = 'column-chooser'; const COLUMN_CHOOSER_BUTTON_CLASS = 'column-chooser-button'; @@ -38,21 +38,17 @@ const COLUMN_CHOOSER_ITEM_CLASS = 'dx-column-chooser-item'; const COLUMN_OPTIONS_USED_IN_ITEMS = ['showInColumnChooser', 'caption', 'allowHiding', 'visible', 'cssClass', 'ownerBand']; -type NodeInternal = Item & { - itemData: { - id: number; - }; - children: NodeInternal[]; -}; - -const processItems = function (that: ColumnChooserView, chooserColumns) { - const items: any = []; +const processItems = ( + that: ColumnChooserView, + chooserColumns: Column[], +): ColumnChooserItem[] => { + const items: ColumnChooserItem[] = []; const isSelectMode = that.isSelectMode(); const isRecursive = that.option('columnChooser.selection.recursive'); if (chooserColumns.length) { each(chooserColumns, (index, column) => { - const item: any = { + const item: ColumnChooserItem = { text: column.caption, cssClass: column.cssClass, allowHiding: column.allowHiding, diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/types.ts b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/types.ts new file mode 100644 index 000000000000..6e7404895b7e --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/types.ts @@ -0,0 +1,19 @@ +import type { Item } from '@js/ui/tree_view'; + +export interface ColumnChooserItem { + expanded: boolean; + disabled: boolean; + parentId: number | null; + text?: string; + cssClass?: string; + allowHiding?: boolean; + id?: number; + selected?: boolean; +} + +export type NodeInternal = Item & { + itemData: { + id: number; + }; + children: NodeInternal[]; +}; From 382425f99980fa32c00e567abbe1c767f99ece4d Mon Sep 17 00:00:00 2001 From: Aliar666 Date: Tue, 27 Oct 2026 00:20:19 +0400 Subject: [PATCH 03/10] Grids: type column chooser popup options --- .../column_chooser/m_column_chooser.ts | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts index e8ec835e8ec8..3773bbd40e29 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts @@ -1,6 +1,6 @@ /* eslint-disable max-classes-per-file */ import messageLocalization from '@js/common/core/localization/message'; -import type { Coordinates as Offset } from '@js/core/renderer'; +import type { Coordinates as Offset, dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; import { deferUpdate } from '@js/core/utils/common'; import type { DeferredObj } from '@js/core/utils/deferred'; @@ -10,12 +10,13 @@ import { each } from '@js/core/utils/iterator'; import { getOuterHeight, getOuterWidth } from '@js/core/utils/size'; import { isDefined } from '@js/core/utils/type'; import Button from '@js/ui/button'; -import type { Properties as PopupProperties } from '@js/ui/popup'; import Popup from '@js/ui/popup/ui.popup'; import TreeView from '@js/ui/tree_view'; import type { DataChange } from '@ts/grids/grid_core/data_controller/types'; import type { RowsView } from '@ts/grids/grid_core/views/rows_view'; import type { BoundingRect } from '@ts/grids/grid_core/views/types'; +import type { InternalPositionConfig } from '@ts/ui/overlay/overlay'; +import type { PopupProperties } from '@ts/ui/popup/popup'; import type { ColumnHeadersView } from '../column_headers/m_column_headers'; import type { ColumnsController } from '../columns_controller/columns_controller'; @@ -79,26 +80,24 @@ export class ColumnChooserController extends modules.ViewController { this._rowsView = this.getView('rowsView'); } - private renderShowColumnChooserButton($element) { - const that = this; - const columnChooserButtonClass = that.addWidgetPrefix(COLUMN_CHOOSER_BUTTON_CLASS); - const columnChooserEnabled = that.option('columnChooser.enabled'); + private renderShowColumnChooserButton($element: dxElementWrapper): void { + const columnChooserButtonClass = this.addWidgetPrefix(COLUMN_CHOOSER_BUTTON_CLASS); + const columnChooserEnabled = this.option('columnChooser.enabled'); const $showColumnChooserButton = $element.find(`.${columnChooserButtonClass}`); - let $columnChooserButton; if (columnChooserEnabled) { if (!$showColumnChooserButton.length) { - $columnChooserButton = $('
') + const $columnChooserButton = $('
') .addClass(columnChooserButtonClass) .appendTo($element); - that._createComponent($columnChooserButton, Button, { + this._createComponent($columnChooserButton, Button, { icon: COLUMN_CHOOSER_ICON_NAME, - onClick() { + onClick: (): void => { // TODO getView - that.getView('columnChooserView').showColumnChooser(); + this.getView('columnChooserView').showColumnChooser(); }, - hint: that.option('columnChooser.title'), + hint: this.option('columnChooser.title'), integrationOptions: {}, }); } else { @@ -109,7 +108,7 @@ export class ColumnChooserController extends modules.ViewController { } } - public getPosition() { + public getPosition(): InternalPositionConfig { const position = this.option('columnChooser.position'); return isDefined(position) ? position : { @@ -159,7 +158,7 @@ export class ColumnChooserView extends ColumnsView { const columnChooserOptions = this.option('columnChooser')!; const popupPosition = this._columnChooserController.getPosition(); - const dxPopupOptions = { + const dxPopupOptions: PopupProperties = { visible: false, shading: false, showCloseButton: true, @@ -176,7 +175,7 @@ export class ColumnChooserView extends ColumnsView { container: columnChooserOptions.container, tabFocusLoopEnabled: true, _ignoreCloseOnChildEscape: true, - } as PopupProperties; + }; if (!isDefined(this._popupContainer)) { this._popupContainer = this._createComponent($element, Popup, dxPopupOptions); From 528d36042796c58ab56a84b84f6e341d19f4c4fa Mon Sep 17 00:00:00 2001 From: Aliar666 Date: Tue, 27 Oct 2026 00:25:57 +0400 Subject: [PATCH 04/10] Grids: type column chooser tree view config --- .../column_chooser/m_column_chooser.ts | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts index 3773bbd40e29..83f652921935 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts @@ -1,5 +1,6 @@ /* eslint-disable max-classes-per-file */ import messageLocalization from '@js/common/core/localization/message'; +import type { DxElement } from '@js/core/element'; import type { Coordinates as Offset, dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; import { deferUpdate } from '@js/core/utils/common'; @@ -11,6 +12,9 @@ import { getOuterHeight, getOuterWidth } from '@js/core/utils/size'; import { isDefined } from '@js/core/utils/type'; import Button from '@js/ui/button'; import Popup from '@js/ui/popup/ui.popup'; +import type { + ContentReadyEvent, OptionChangedEvent, Properties as TreeViewProperties, SelectionChangedEvent, +} from '@js/ui/tree_view'; import TreeView from '@js/ui/tree_view'; import type { DataChange } from '@ts/grids/grid_core/data_controller/types'; import type { RowsView } from '@ts/grids/grid_core/views/rows_view'; @@ -234,8 +238,7 @@ export class ColumnChooserView extends ColumnsView { return Deferred().resolve(); } - private _renderTreeView() { - const that = this; + private _renderTreeView(): void { const $container = this._popupContainer?.$content(); if (!$container) { @@ -245,7 +248,7 @@ export class ColumnChooserView extends ColumnsView { const columnChooser = this.option('columnChooser')!; const isSelectMode = this.isSelectMode(); - const treeViewConfig: any = { + const treeViewConfig: TreeViewProperties = { dataStructure: 'plain', activeStateEnabled: true, focusStateEnabled: true, @@ -258,7 +261,10 @@ export class ColumnChooserView extends ColumnsView { searchEditorOptions: columnChooser.search?.editorOptions, }; - extend(treeViewConfig, isSelectMode ? this._prepareSelectModeConfig() : this._prepareDragModeConfig()); + extend( + treeViewConfig, + isSelectMode ? this._prepareSelectModeConfig() : this._prepareDragModeConfig(), + ); if (this._columnChooserList) { if (!treeViewConfig.searchEnabled) { @@ -266,43 +272,46 @@ export class ColumnChooserView extends ColumnsView { } this._columnChooserList.option(treeViewConfig); - // we need to set items after setting selectNodesRecursive, so they will be processed correctly inside TreeView + // we need to set items after setting selectNodesRecursive, + // so they will be processed correctly inside TreeView this._updateItems(); } else { this._columnChooserList = this._createComponent($container, TreeView, treeViewConfig); - // we need to set items after setting selectNodesRecursive, so they will be processed correctly inside TreeView + // we need to set items after setting selectNodesRecursive, + // so they will be processed correctly inside TreeView this._updateItems(); let scrollTop = 0; - this._columnChooserList.on('optionChanged', (e) => { + this._columnChooserList.on('optionChanged', (e: OptionChangedEvent) => { const scrollable = e.component.getScrollable(); scrollTop = scrollable.scrollTop(); }); - this._columnChooserList.on('contentReady', (e) => { + this._columnChooserList.on('contentReady', (e: ContentReadyEvent) => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises -- fire-and-forget deferUpdate(() => { const scrollable = e.component.getScrollable(); scrollable.scrollTo({ top: scrollTop }); - that.renderCompleted.fire(); + this.renderCompleted.fire(); }); }); } } - private _prepareDragModeConfig() { + private _prepareDragModeConfig(): TreeViewProperties { const columnChooserOptions = this.option('columnChooser')!; return { noDataText: columnChooserOptions.emptyPanelText, activeStateEnabled: false, hoverStateEnabled: false, - itemTemplate(data, index, item) { + itemTemplate(data: ColumnChooserItem, index: number, item: DxElement): void { $(item) - .text(data.text) + .text(data.text ?? '') .parent() - .addClass(data.cssClass) + .addClass(data.cssClass ?? '') .addClass(COLUMN_CHOOSER_ITEM_CLASS); }, }; @@ -355,9 +364,10 @@ export class ColumnChooserView extends ColumnsView { return getFlattenedNodesRecursive(nodes, []).reverse(); } - private _prepareSelectModeConfig() { + private _prepareSelectModeConfig(): TreeViewProperties { const selectionOptions = this.option('columnChooser.selection') ?? {}; - const selectionChangedHandler = (e) => { + const selectionChangedHandler = (e: SelectionChangedEvent): void => { + // @ts-expect-error getNodes() is typed with optional children and itemData const nodes = this._getOrderedFlatNodes(e.component.getNodes()); this.component.beginUpdate(); From d1501da78a8830679722a76b0db8d58960e7229a Mon Sep 17 00:00:00 2001 From: Aliar666 Date: Tue, 27 Oct 2026 01:24:06 +0400 Subject: [PATCH 05/10] Grids: type column chooser view API --- .../column_chooser/m_column_chooser.ts | 48 +++++++++---------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts index 83f652921935..dfe2100b2a66 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts @@ -24,10 +24,10 @@ import type { PopupProperties } from '@ts/ui/popup/popup'; import type { ColumnHeadersView } from '../column_headers/m_column_headers'; import type { ColumnsController } from '../columns_controller/columns_controller'; -import type { Column, DropLocationNames } from '../columns_controller/types'; +import type { Column, ColumnsChanges, DropLocationNames } from '../columns_controller/types'; import type { HeaderPanel } from '../header_panel/header_panel'; import modules from '../modules/modules'; -import type { ModuleType } from '../types'; +import type { ModuleType, OptionChanged } from '../types'; import { ColumnsView } from '../views/columns_view'; import { defaultOptions } from './const'; import type { ColumnChooserItem, NodeInternal } from './types'; @@ -135,7 +135,7 @@ export class ColumnChooserView extends ColumnsView { private _isPopupContainerShown?: boolean; - public optionChanged(args) { + public optionChanged(args: OptionChanged): void { switch (args.name) { case 'columnChooser': this._initializePopupContainer(); @@ -147,11 +147,11 @@ export class ColumnChooserView extends ColumnsView { } } - public publicMethods() { + public publicMethods(): string[] { return ['showColumnChooser', 'hideColumnChooser']; } - protected _resizeCore() { + protected _resizeCore(): void { } @@ -396,14 +396,16 @@ export class ColumnChooserView extends ColumnsView { this._columnChooserList?.option('items', items); } - private _updateItemsSelection(columnIndices) { + private _updateItemsSelection(columnIndices: number[] | undefined): void { const columnChooserList = this._columnChooserList; if (!columnChooserList) { return; } - const changedColumns = columnIndices?.map((columnIndex) => this._columnsController.columnOption(columnIndex)); + // @ts-expect-error the changed columns exist + const changedColumns: Column[] | undefined = columnIndices + ?.map((columnIndex) => this._columnsController.columnOption(columnIndex)); columnChooserList.beginUpdate(); changedColumns?.forEach(({ visible, index }) => { @@ -416,7 +418,7 @@ export class ColumnChooserView extends ColumnsView { columnChooserList.endUpdate(); } - protected _columnOptionChanged(changes): void { + protected _columnOptionChanged(changes: ColumnsChanges): void { super._columnOptionChanged(changes); const { optionNames } = changes; @@ -434,7 +436,7 @@ export class ColumnChooserView extends ColumnsView { : changes.columnIndices; const hasItemsOptionNames = COLUMN_OPTIONS_USED_IN_ITEMS .some((optionName) => optionNames[optionName]); - const needUpdate: boolean = hasItemsOptionNames + const needUpdate: boolean | undefined = hasItemsOptionNames || (changes.changeTypes.columns && optionNames.all); if (!needUpdate) { @@ -457,34 +459,30 @@ export class ColumnChooserView extends ColumnsView { return optionKeys.length === 1 && optionKeys[0] === 'visible'; } - public getColumnElements() { - const result: any = []; - + public getColumnElements(): dxElementWrapper { const isSelectMode = this.isSelectMode(); const chooserColumns = this._columnsController.getChooserColumns(isSelectMode); const $content = this._popupContainer?.$content(); const $nodes = $content?.find('.dx-treeview-node'); - if ($nodes) { - chooserColumns.forEach((column) => { - const $node = $nodes.filter(`[data-item-id = '${column.index}']`); - const item = $node.length ? $node.children(`.${COLUMN_CHOOSER_ITEM_CLASS}`).get(0) : null; - result.push(item); - }); + if (!$nodes) { + return $([]); } - return $(result); + return $(chooserColumns.map((column) => $nodes + .filter(`[data-item-id = '${column.index}']`) + .children(`.${COLUMN_CHOOSER_ITEM_CLASS}`).get(0))); } - public getName() { + public getName(): string { return 'columnChooser'; } - public getColumns() { + public getColumns(): Column[] { return this._columnsController.getChooserColumns(); } - public allowDragging(column) { + public allowDragging(column: Column): boolean | undefined { const isParentColumnVisible = this._columnsController.isParentColumnVisible(column.index); const isColumnHidden = !column.visible && column.allowHiding; @@ -541,15 +539,15 @@ export class ColumnChooserView extends ColumnsView { return popupContainer?.option('visible'); } - public isSelectMode() { + public isSelectMode(): boolean { return this.option('columnChooser.mode') === 'select'; } - public hasHiddenColumns() { + public hasHiddenColumns(): boolean { const isEnabled = this.option('columnChooser.enabled'); const hiddenColumns = this.getColumns().filter((column) => !column.visible); - return isEnabled && hiddenColumns.length; + return Boolean(isEnabled) && hiddenColumns.length > 0; } } From aefaca25d955859815560dcda55f180966c656e8 Mon Sep 17 00:00:00 2001 From: Aliar666 Date: Tue, 27 Oct 2026 01:30:13 +0400 Subject: [PATCH 06/10] Grids: type column chooser extenders --- .../column_chooser/m_column_chooser.ts | 59 ++++++++++++------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts index dfe2100b2a66..167c99c98560 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts @@ -1,5 +1,6 @@ /* eslint-disable max-classes-per-file */ import messageLocalization from '@js/common/core/localization/message'; +import type { ColumnChooser } from '@js/common/grids'; import type { DxElement } from '@js/core/element'; import type { Coordinates as Offset, dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; @@ -10,6 +11,7 @@ import { extend } from '@js/core/utils/extend'; import { each } from '@js/core/utils/iterator'; import { getOuterHeight, getOuterWidth } from '@js/core/utils/size'; import { isDefined } from '@js/core/utils/type'; +import type { InitializedEvent as ButtonInitializedEvent } from '@js/ui/button'; import Button from '@js/ui/button'; import Popup from '@js/ui/popup/ui.popup'; import type { @@ -19,12 +21,15 @@ import TreeView from '@js/ui/tree_view'; import type { DataChange } from '@ts/grids/grid_core/data_controller/types'; import type { RowsView } from '@ts/grids/grid_core/views/rows_view'; import type { BoundingRect } from '@ts/grids/grid_core/views/types'; +import type { ToolbarItem } from '@ts/grids/new/grid_core/toolbar/types'; import type { InternalPositionConfig } from '@ts/ui/overlay/overlay'; import type { PopupProperties } from '@ts/ui/popup/popup'; import type { ColumnHeadersView } from '../column_headers/m_column_headers'; import type { ColumnsController } from '../columns_controller/columns_controller'; -import type { Column, ColumnsChanges, DropLocationNames } from '../columns_controller/types'; +import type { + Column, ColumnIndex, ColumnsChanges, DropLocationNames, +} from '../columns_controller/types'; import type { HeaderPanel } from '../header_panel/header_panel'; import modules from '../modules/modules'; import type { ModuleType, OptionChanged } from '../types'; @@ -551,27 +556,30 @@ export class ColumnChooserView extends ColumnsView { } } -const headerPanel = (Base: ModuleType) => class ColumnChooserHeaderPanelExtender extends Base { - protected _getToolbarItems() { +const headerPanel = ( + Base: ModuleType, +): ModuleType => class ColumnChooserHeaderPanelExtender extends Base { + protected _getToolbarItems(): ToolbarItem[] { const items = super._getToolbarItems(); return this._appendColumnChooserItem(items); } - private _appendColumnChooserItem(items) { - const that = this; - const columnChooserEnabled = that.option('columnChooser.enabled'); + private _appendColumnChooserItem(items: ToolbarItem[]): ToolbarItem[] { + const columnChooserEnabled = this.option('columnChooser.enabled'); if (columnChooserEnabled) { - const onClickHandler = function () { + const onClickHandler = (): void => { // TODO getView - that.component.getView('columnChooserView').showColumnChooser(); + this.component.getView('columnChooserView').showColumnChooser(); }; - const onInitialized = function (e) { - $(e.element).addClass(that.getToolbarButtonClass(that.addWidgetPrefix(COLUMN_CHOOSER_BUTTON_CLASS))); + const onInitialized = (e: ButtonInitializedEvent): void => { + $(e.element).addClass(this.getToolbarButtonClass( + this.addWidgetPrefix(COLUMN_CHOOSER_BUTTON_CLASS), + )); }; - const hintText = that.option('columnChooser.title'); - const toolbarItem = { + const hintText = this.option('columnChooser.title'); + const toolbarItem: ToolbarItem = { widget: 'dxButton', options: { icon: COLUMN_CHOOSER_ICON_NAME, @@ -594,7 +602,7 @@ const headerPanel = (Base: ModuleType) => class ColumnChooserHeader return items; } - public optionChanged(args) { + public optionChanged(args: OptionChanged): void { switch (args.name) { case 'columnChooser': this._invalidate(); @@ -606,31 +614,38 @@ const headerPanel = (Base: ModuleType) => class ColumnChooserHeader } }; -const columns = (Base: ModuleType) => class ColumnsChooserColumnsControllerExtender extends Base { +const columns = ( + Base: ModuleType, +): ModuleType => class ColumnsChooserColumnsControllerExtender extends Base { public allowMoveColumn( - fromVisibleIndex, - toVisibleIndex, + fromVisibleIndex: ColumnIndex, + toVisibleIndex: ColumnIndex, sourceLocation: DropLocationNames, targetLocation: DropLocationNames, - ) { + ): boolean | undefined { const isSelectMode = this.option('columnChooser.mode') === 'select'; const isMoveColumnDisallowed = isSelectMode && targetLocation === 'columnChooser'; - return isMoveColumnDisallowed ? false : super.allowMoveColumn(fromVisibleIndex, toVisibleIndex, sourceLocation, targetLocation); + return isMoveColumnDisallowed + ? false + : super.allowMoveColumn(fromVisibleIndex, toVisibleIndex, sourceLocation, targetLocation); } }; -const columnHeadersView = (Base: ModuleType) => class ColumnChooserColumnHeadersExtender extends Base { - public allowDragging(column) { +const columnHeadersView = ( + Base: ModuleType, +): ModuleType => class ColumnChooserColumnHeadersExtender extends Base { + public allowDragging(column: Column): boolean | undefined { const isDragMode = !this._columnChooserView.isSelectMode(); const isColumnChooserVisible = this._columnChooserView.isColumnChooserVisible(); - return (isDragMode && isColumnChooserVisible && column.allowHiding) || super.allowDragging(column); + return (isDragMode && isColumnChooserVisible && column.allowHiding) + || super.allowDragging(column); } }; export const columnChooserModule = { - defaultOptions() { + defaultOptions(): { columnChooser: ColumnChooser } { return defaultOptions; }, controllers: { From 824914d87299e31ab77a698c1181199f43227b22 Mon Sep 17 00:00:00 2001 From: Aliar666 Date: Tue, 27 Oct 2026 01:38:21 +0400 Subject: [PATCH 07/10] Grids: drop non-null assertions in column chooser --- .../column_chooser/m_column_chooser.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts index 167c99c98560..fe0d7832083e 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts @@ -164,7 +164,7 @@ export class ColumnChooserView extends ColumnsView { const columnChooserClass = this.addWidgetPrefix(COLUMN_CHOOSER_CLASS); // @ts-expect-error the view is rendered here const $element = this.element().addClass(columnChooserClass); - const columnChooserOptions = this.option('columnChooser')!; + const columnChooserOptions = this.option('columnChooser'); const popupPosition = this._columnChooserController.getPosition(); const dxPopupOptions: PopupProperties = { @@ -175,13 +175,13 @@ export class ColumnChooserView extends ColumnsView { resizeEnabled: true, wrapperAttr: { class: columnChooserClass }, toolbarItems: [ - { text: columnChooserOptions.title, toolbar: 'top', location: 'before' }, + { text: columnChooserOptions?.title, toolbar: 'top', location: 'before' }, ], position: popupPosition, - width: columnChooserOptions.width, - height: columnChooserOptions.height, + width: columnChooserOptions?.width, + height: columnChooserOptions?.height, rtlEnabled: this.option('rtlEnabled'), - container: columnChooserOptions.container, + container: columnChooserOptions?.container, tabFocusLoopEnabled: true, _ignoreCloseOnChildEscape: true, }; @@ -250,7 +250,7 @@ export class ColumnChooserView extends ColumnsView { return; } - const columnChooser = this.option('columnChooser')!; + const columnChooser = this.option('columnChooser'); const isSelectMode = this.isSelectMode(); const treeViewConfig: TreeViewProperties = { @@ -261,9 +261,9 @@ export class ColumnChooserView extends ColumnsView { itemTemplate: 'item', showCheckBoxesMode: 'none', rootValue: null, - searchEnabled: columnChooser.search?.enabled, - searchTimeout: columnChooser.search?.timeout, - searchEditorOptions: columnChooser.search?.editorOptions, + searchEnabled: columnChooser?.search?.enabled, + searchTimeout: columnChooser?.search?.timeout, + searchEditorOptions: columnChooser?.search?.editorOptions, }; extend( @@ -306,10 +306,10 @@ export class ColumnChooserView extends ColumnsView { } private _prepareDragModeConfig(): TreeViewProperties { - const columnChooserOptions = this.option('columnChooser')!; + const columnChooserOptions = this.option('columnChooser'); return { - noDataText: columnChooserOptions.emptyPanelText, + noDataText: columnChooserOptions?.emptyPanelText, activeStateEnabled: false, hoverStateEnabled: false, itemTemplate(data: ColumnChooserItem, index: number, item: DxElement): void { From bbdca5f6a77e91c46faadc8a4ab60c19b7685386 Mon Sep 17 00:00:00 2001 From: Aliar666 Date: Tue, 27 Oct 2026 02:13:21 +0400 Subject: [PATCH 08/10] Grids: fix column chooser lint warning --- .../grids/grid_core/column_chooser/m_column_chooser.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts index fe0d7832083e..b4a5da25aa74 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts @@ -549,10 +549,10 @@ export class ColumnChooserView extends ColumnsView { } public hasHiddenColumns(): boolean { - const isEnabled = this.option('columnChooser.enabled'); + const isEnabled = !!this.option('columnChooser.enabled'); const hiddenColumns = this.getColumns().filter((column) => !column.visible); - return Boolean(isEnabled) && hiddenColumns.length > 0; + return isEnabled && hiddenColumns.length > 0; } } @@ -637,9 +637,9 @@ const columnHeadersView = ( ): ModuleType => class ColumnChooserColumnHeadersExtender extends Base { public allowDragging(column: Column): boolean | undefined { const isDragMode = !this._columnChooserView.isSelectMode(); - const isColumnChooserVisible = this._columnChooserView.isColumnChooserVisible(); + const isColumnChooserVisible = !!this._columnChooserView.isColumnChooserVisible(); - return (isDragMode && isColumnChooserVisible && column.allowHiding) + return (isDragMode && isColumnChooserVisible && !!column.allowHiding) || super.allowDragging(column); } }; From fecf90b1d9fe9c8d2205c3bfdab5f90cd0e91bd2 Mon Sep 17 00:00:00 2001 From: Aliar666 Date: Tue, 27 Oct 2026 02:45:09 +0400 Subject: [PATCH 09/10] Grids: simplify column chooser code --- .../column_chooser.integration.test.ts | 39 +++++++++++ .../column_chooser/m_column_chooser.ts | 65 +++++++++---------- .../column_headers/m_column_headers.ts | 2 +- 3 files changed, 71 insertions(+), 35 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/__tests__/column_chooser.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/__tests__/column_chooser.integration.test.ts index d1a605fc1e83..668677be7d78 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/__tests__/column_chooser.integration.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/__tests__/column_chooser.integration.test.ts @@ -260,3 +260,42 @@ describe('Bugs', () => { }); }); }); + +describe('ColumnChooserController', () => { + beforeEach(() => { + beforeTest(); + jest.spyOn(errors, 'log').mockImplementation(jest.fn()); + jest.spyOn(errors, 'Error').mockImplementation(() => ({}) as ReturnType); + }); + afterEach(afterTest); + + describe('getPosition', () => { + it('should return the default position when columnChooser.position is not set', async () => { + const { instance } = await createDataGrid({ + dataSource: [], + columns: ['id'], + columnChooser: { enabled: true }, + }); + + expect(instance.getController('columnChooser').getPosition()).toEqual({ + my: 'right bottom', + at: 'right bottom', + of: instance.getView('rowsView').element(), + collision: 'fit', + offset: '-2 -2', + boundaryOffset: '2 2', + }); + }); + + it('should return columnChooser.position when it is set', async () => { + const position = { my: 'left top', at: 'left top', of: '#gridContainer' } as const; + const { instance } = await createDataGrid({ + dataSource: [], + columns: ['id'], + columnChooser: { enabled: true, position }, + }); + + expect(instance.getController('columnChooser').getPosition()).toEqual(position); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts index b4a5da25aa74..94e39dd5a3d3 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts @@ -2,13 +2,12 @@ import messageLocalization from '@js/common/core/localization/message'; import type { ColumnChooser } from '@js/common/grids'; import type { DxElement } from '@js/core/element'; -import type { Coordinates as Offset, dxElementWrapper } from '@js/core/renderer'; +import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; import { deferUpdate } from '@js/core/utils/common'; import type { DeferredObj } from '@js/core/utils/deferred'; import { Deferred } from '@js/core/utils/deferred'; import { extend } from '@js/core/utils/extend'; -import { each } from '@js/core/utils/iterator'; import { getOuterHeight, getOuterWidth } from '@js/core/utils/size'; import { isDefined } from '@js/core/utils/type'; import type { InitializedEvent as ButtonInitializedEvent } from '@js/ui/button'; @@ -46,39 +45,36 @@ const COLUMN_CHOOSER_SELECT_CLASS = 'column-chooser-mode-select'; const COLUMN_CHOOSER_ICON_NAME = 'column-chooser'; const COLUMN_CHOOSER_ITEM_CLASS = 'dx-column-chooser-item'; -const COLUMN_OPTIONS_USED_IN_ITEMS = ['showInColumnChooser', 'caption', 'allowHiding', 'visible', 'cssClass', 'ownerBand']; +const COLUMN_OPTIONS_USED_IN_ITEMS: (keyof Column)[] = [ + 'showInColumnChooser', 'caption', 'allowHiding', 'visible', 'cssClass', 'ownerBand', +]; const processItems = ( that: ColumnChooserView, chooserColumns: Column[], ): ColumnChooserItem[] => { - const items: ColumnChooserItem[] = []; const isSelectMode = that.isSelectMode(); const isRecursive = that.option('columnChooser.selection.recursive'); - if (chooserColumns.length) { - each(chooserColumns, (index, column) => { - const item: ColumnChooserItem = { - text: column.caption, - cssClass: column.cssClass, - allowHiding: column.allowHiding, - expanded: true, - id: column.index, - disabled: column.allowHiding === false, - parentId: isDefined(column.ownerBand) ? column.ownerBand : null, - }; + return chooserColumns.map((column) => { + const item: ColumnChooserItem = { + text: column.caption, + cssClass: column.cssClass, + allowHiding: column.allowHiding, + expanded: true, + id: column.index, + disabled: column.allowHiding === false, + parentId: column.ownerBand ?? null, + }; - const isRecursiveWithColumns = isRecursive && column.hasColumns; + const isRecursiveWithColumns = isRecursive && column.hasColumns; - if (isSelectMode && !isRecursiveWithColumns) { - item.selected = column.visible; - } - - items.push(item); - }); - } + if (isSelectMode && !isRecursiveWithColumns) { + item.selected = column.visible; + } - return items; + return item; + }); }; export class ColumnChooserController extends modules.ViewController { @@ -120,7 +116,7 @@ export class ColumnChooserController extends modules.ViewController { public getPosition(): InternalPositionConfig { const position = this.option('columnChooser.position'); - return isDefined(position) ? position : { + return position ?? { my: 'right bottom', at: 'right bottom', of: this._rowsView?.element(), @@ -498,15 +494,16 @@ export class ColumnChooserView extends ColumnsView { const container = this._popupContainer?.$overlayContent(); if (container?.is(':visible')) { - // @ts-expect-error offset() is typed as possibly undefined - const offset: Offset = container.offset(); - - return { - left: offset.left, - top: offset.top, - right: offset.left + Number(getOuterWidth(container)), - bottom: offset.top + Number(getOuterHeight(container)), - }; + const offset = container.offset(); + + if (offset) { + return { + left: offset.left, + top: offset.top, + right: offset.left + Number(getOuterWidth(container)), + bottom: offset.top + Number(getOuterHeight(container)), + }; + } } return null; diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_headers/m_column_headers.ts b/packages/devextreme/js/__internal/grids/grid_core/column_headers/m_column_headers.ts index efcc9f91d5a4..832525abf3df 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/column_headers/m_column_headers.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/column_headers/m_column_headers.ts @@ -176,7 +176,7 @@ export class ColumnHeadersView extends ColumnContextMenuMixin(ColumnsView) { } private _getEmptyHeaderText(): string { - const hasHiddenColumns = Boolean(this._columnChooserView.hasHiddenColumns()); + const hasHiddenColumns = this._columnChooserView.hasHiddenColumns(); const hasGroupedColumns = Boolean(this._headerPanelView.hasGroupedColumns()); switch (true) { From 69aa8c3a7d35c8e1c3bd847648da20840273a8e0 Mon Sep 17 00:00:00 2001 From: Aliar666 Date: Tue, 27 Oct 2026 17:17:14 +0400 Subject: [PATCH 10/10] Fix comments --- .../grids/grid_core/column_chooser/const.ts | 14 +++ .../column_chooser/m_column_chooser.ts | 108 ++++++++---------- 2 files changed, 62 insertions(+), 60 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/const.ts b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/const.ts index 4b27729f7894..84f5b531da15 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/const.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/const.ts @@ -1,6 +1,20 @@ import type { ColumnChooser } from '@js/common/grids'; import messageLocalization from '@js/localization/message'; +export const CLASSES = { + columnChooser: 'column-chooser', + columnChooserButton: 'column-chooser-button', + columnChooserList: 'column-chooser-list', + columnChooserPlain: 'column-chooser-plain', + columnChooserModeDrag: 'column-chooser-mode-drag', + columnChooserModeSelect: 'column-chooser-mode-select', + columnChooserItem: 'dx-column-chooser-item', +}; + +export const ICONS = { + columnChooser: 'column-chooser', +}; + export const defaultOptions = { columnChooser: { enabled: false, diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts index 94e39dd5a3d3..70c30a3eb21a 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts @@ -23,6 +23,7 @@ import type { BoundingRect } from '@ts/grids/grid_core/views/types'; import type { ToolbarItem } from '@ts/grids/new/grid_core/toolbar/types'; import type { InternalPositionConfig } from '@ts/ui/overlay/overlay'; import type { PopupProperties } from '@ts/ui/popup/popup'; +import { NODE_CLASS } from '@ts/ui/tree_view/tree_view.base'; import type { ColumnHeadersView } from '../column_headers/m_column_headers'; import type { ColumnsController } from '../columns_controller/columns_controller'; @@ -33,50 +34,13 @@ import type { HeaderPanel } from '../header_panel/header_panel'; import modules from '../modules/modules'; import type { ModuleType, OptionChanged } from '../types'; import { ColumnsView } from '../views/columns_view'; -import { defaultOptions } from './const'; +import { CLASSES, defaultOptions, ICONS } from './const'; import type { ColumnChooserItem, NodeInternal } from './types'; -const COLUMN_CHOOSER_CLASS = 'column-chooser'; -const COLUMN_CHOOSER_BUTTON_CLASS = 'column-chooser-button'; -const COLUMN_CHOOSER_LIST_CLASS = 'column-chooser-list'; -const COLUMN_CHOOSER_PLAIN_CLASS = 'column-chooser-plain'; -const COLUMN_CHOOSER_DRAG_CLASS = 'column-chooser-mode-drag'; -const COLUMN_CHOOSER_SELECT_CLASS = 'column-chooser-mode-select'; -const COLUMN_CHOOSER_ICON_NAME = 'column-chooser'; -const COLUMN_CHOOSER_ITEM_CLASS = 'dx-column-chooser-item'; - const COLUMN_OPTIONS_USED_IN_ITEMS: (keyof Column)[] = [ 'showInColumnChooser', 'caption', 'allowHiding', 'visible', 'cssClass', 'ownerBand', ]; -const processItems = ( - that: ColumnChooserView, - chooserColumns: Column[], -): ColumnChooserItem[] => { - const isSelectMode = that.isSelectMode(); - const isRecursive = that.option('columnChooser.selection.recursive'); - - return chooserColumns.map((column) => { - const item: ColumnChooserItem = { - text: column.caption, - cssClass: column.cssClass, - allowHiding: column.allowHiding, - expanded: true, - id: column.index, - disabled: column.allowHiding === false, - parentId: column.ownerBand ?? null, - }; - - const isRecursiveWithColumns = isRecursive && column.hasColumns; - - if (isSelectMode && !isRecursiveWithColumns) { - item.selected = column.visible; - } - - return item; - }); -}; - export class ColumnChooserController extends modules.ViewController { private _rowsView!: RowsView; @@ -86,7 +50,7 @@ export class ColumnChooserController extends modules.ViewController { } private renderShowColumnChooserButton($element: dxElementWrapper): void { - const columnChooserButtonClass = this.addWidgetPrefix(COLUMN_CHOOSER_BUTTON_CLASS); + const columnChooserButtonClass = this.addWidgetPrefix(CLASSES.columnChooserButton); const columnChooserEnabled = this.option('columnChooser.enabled'); const $showColumnChooserButton = $element.find(`.${columnChooserButtonClass}`); @@ -97,7 +61,7 @@ export class ColumnChooserController extends modules.ViewController { .appendTo($element); this._createComponent($columnChooserButton, Button, { - icon: COLUMN_CHOOSER_ICON_NAME, + icon: ICONS.columnChooser, onClick: (): void => { // TODO getView this.getView('columnChooserView').showColumnChooser(); @@ -157,7 +121,7 @@ export class ColumnChooserView extends ColumnsView { } private _initializePopupContainer(): Popup { - const columnChooserClass = this.addWidgetPrefix(COLUMN_CHOOSER_CLASS); + const columnChooserClass = this.addWidgetPrefix(CLASSES.columnChooser); // @ts-expect-error the view is rendered here const $element = this.element().addClass(columnChooserClass); const columnChooserOptions = this.option('columnChooser'); @@ -215,13 +179,13 @@ export class ColumnChooserView extends ColumnsView { }); popupContainer.$wrapper() - ?.toggleClass(this.addWidgetPrefix(COLUMN_CHOOSER_DRAG_CLASS), !isSelectMode) - .toggleClass(this.addWidgetPrefix(COLUMN_CHOOSER_SELECT_CLASS), isSelectMode); + ?.toggleClass(this.addWidgetPrefix(CLASSES.columnChooserModeDrag), !isSelectMode) + .toggleClass(this.addWidgetPrefix(CLASSES.columnChooserModeSelect), isSelectMode); - popupContainer.$content()?.addClass(this.addWidgetPrefix(COLUMN_CHOOSER_LIST_CLASS)); + popupContainer.$content()?.addClass(this.addWidgetPrefix(CLASSES.columnChooserList)); if (isSelectMode && !isBandColumnsUsed) { - popupContainer.$content()?.addClass(this.addWidgetPrefix(COLUMN_CHOOSER_PLAIN_CLASS)); + popupContainer.$content()?.addClass(this.addWidgetPrefix(CLASSES.columnChooserPlain)); } } @@ -313,7 +277,7 @@ export class ColumnChooserView extends ColumnsView { .text(data.text ?? '') .parent() .addClass(data.cssClass ?? '') - .addClass(COLUMN_CHOOSER_ITEM_CLASS); + .addClass(CLASSES.columnChooserItem); }, }; } @@ -389,10 +353,35 @@ export class ColumnChooserView extends ColumnsView { }; } + private processItems(chooserColumns: Column[]): ColumnChooserItem[] { + const isSelectMode = this.isSelectMode(); + const isRecursive = this.option('columnChooser.selection.recursive'); + + return chooserColumns.map((column) => { + const item: ColumnChooserItem = { + text: column.caption, + cssClass: column.cssClass, + allowHiding: column.allowHiding, + expanded: true, + id: column.index, + disabled: column.allowHiding === false, + parentId: column.ownerBand ?? null, + }; + + const isRecursiveWithColumns = isRecursive && column.hasColumns; + + if (isSelectMode && !isRecursiveWithColumns) { + item.selected = column.visible; + } + + return item; + }); + } + private _updateItems(): void { const isSelectMode = this.isSelectMode(); const chooserColumns = this._columnsController.getChooserColumns(isSelectMode); - const items = processItems(this, chooserColumns); + const items = this.processItems(chooserColumns); this._columnChooserList?.option('items', items); } @@ -404,9 +393,9 @@ export class ColumnChooserView extends ColumnsView { return; } - // @ts-expect-error the changed columns exist - const changedColumns: Column[] | undefined = columnIndices - ?.map((columnIndex) => this._columnsController.columnOption(columnIndex)); + const changedColumns = columnIndices + ?.map((columnIndex) => this._columnsController.columnOption(columnIndex)) + .filter(isDefined); columnChooserList.beginUpdate(); changedColumns?.forEach(({ visible, index }) => { @@ -437,8 +426,7 @@ export class ColumnChooserView extends ColumnsView { : changes.columnIndices; const hasItemsOptionNames = COLUMN_OPTIONS_USED_IN_ITEMS .some((optionName) => optionNames[optionName]); - const needUpdate: boolean | undefined = hasItemsOptionNames - || (changes.changeTypes.columns && optionNames.all); + const needUpdate = hasItemsOptionNames || !!(changes.changeTypes.columns && optionNames.all); if (!needUpdate) { return; @@ -464,15 +452,15 @@ export class ColumnChooserView extends ColumnsView { const isSelectMode = this.isSelectMode(); const chooserColumns = this._columnsController.getChooserColumns(isSelectMode); const $content = this._popupContainer?.$content(); - const $nodes = $content?.find('.dx-treeview-node'); + const $nodes = $content?.find(`.${NODE_CLASS}`); if (!$nodes) { - return $([]); + return $(); } return $(chooserColumns.map((column) => $nodes .filter(`[data-item-id = '${column.index}']`) - .children(`.${COLUMN_CHOOSER_ITEM_CLASS}`).get(0))); + .children(`.${CLASSES.columnChooserItem}`).get(0))); } public getName(): string { @@ -535,10 +523,10 @@ export class ColumnChooserView extends ColumnsView { } } - public isColumnChooserVisible(): boolean | undefined { + public isColumnChooserVisible(): boolean { const popupContainer = this._popupContainer; - return popupContainer?.option('visible'); + return !!popupContainer?.option('visible'); } public isSelectMode(): boolean { @@ -572,14 +560,14 @@ const headerPanel = ( }; const onInitialized = (e: ButtonInitializedEvent): void => { $(e.element).addClass(this.getToolbarButtonClass( - this.addWidgetPrefix(COLUMN_CHOOSER_BUTTON_CLASS), + this.addWidgetPrefix(CLASSES.columnChooserButton), )); }; const hintText = this.option('columnChooser.title'); const toolbarItem: ToolbarItem = { widget: 'dxButton', options: { - icon: COLUMN_CHOOSER_ICON_NAME, + icon: ICONS.columnChooser, onClick: onClickHandler, hint: hintText, text: hintText, @@ -634,7 +622,7 @@ const columnHeadersView = ( ): ModuleType => class ColumnChooserColumnHeadersExtender extends Base { public allowDragging(column: Column): boolean | undefined { const isDragMode = !this._columnChooserView.isSelectMode(); - const isColumnChooserVisible = !!this._columnChooserView.isColumnChooserVisible(); + const isColumnChooserVisible = this._columnChooserView.isColumnChooserVisible(); return (isDragMode && isColumnChooserVisible && !!column.allowHiding) || super.allowDragging(column);