Skip to content

Commit 7fc0418

Browse files
authored
T1306840 - DataGrid - TagBox in the filter row doesn't show selected tags if values are numbers (#33080)
1 parent ab82696 commit 7fc0418

File tree

9 files changed

+185
-21
lines changed

9 files changed

+185
-21
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const SELECTORS = {
2+
editorCell: 'dx-editor-cell',
3+
editorContainer: 'dx-editor-container',
4+
widget: 'dx-widget',
5+
};
6+
7+
export class FilterCellModel {
8+
constructor(protected readonly root: HTMLElement | null) {}
9+
10+
public getElement(): HTMLElement | null {
11+
return this.root;
12+
}
13+
14+
public getEditor<T>(EditorModel: new (element: HTMLElement) => T): T {
15+
const editorElement = this.root?.querySelector(`.${SELECTORS.editorContainer} .${SELECTORS.widget}`) as HTMLElement;
16+
return new EditorModel(editorElement);
17+
}
18+
}

packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/model/grid_core.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ import { ConfirmationDialogModel } from './confirmation_dialog';
1616
import { EditFormModel } from './edit_form';
1717
import { FilterPanelModel } from './filter_panel';
1818
import { DataRowModel } from './row/data_row';
19+
import { FilterRowModel } from './row/filter_row';
1920
import { GroupRowModel } from './row/group_row';
2021

2122
const SELECTORS = {
2223
headerRowClass: 'dx-header-row',
2324
dataRowClass: 'dx-data-row',
2425
groupRowClass: 'dx-group-row',
26+
filterRowClass: 'filter-row',
2527
scrollableContainer: 'dx-scrollable-container',
2628
aiDialog: 'dx-aidialog',
2729
aiPromptEditor: 'dx-ai-prompt-editor',
@@ -98,6 +100,13 @@ export abstract class GridCoreModel<TInstance = GridBase | CardView> {
98100
);
99101
}
100102

103+
public getFilterRow(): FilterRowModel {
104+
const filterRowElement = this.root.querySelector(
105+
`.${this.addWidgetPrefix(SELECTORS.filterRowClass)}`,
106+
);
107+
return new FilterRowModel(filterRowElement as HTMLElement);
108+
}
109+
101110
public getGroupRows(): NodeListOf<HTMLElement> {
102111
return this.root.querySelectorAll(`.${SELECTORS.groupRowClass}`);
103112
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export class BaseRowModel {
2+
constructor(protected readonly root: HTMLElement | null) {}
3+
4+
public getElement(): HTMLElement | null {
5+
return this.root;
6+
}
7+
8+
public getCells(): NodeListOf<HTMLElement> {
9+
return this.root?.querySelectorAll('td') as NodeListOf<HTMLElement>;
10+
}
11+
12+
public getCell(columnIndex: number): HTMLElement | null {
13+
return this.getCells()?.[columnIndex] ?? null;
14+
}
15+
}

packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/model/row/data_row.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
1+
import { BaseRowModel } from './base_row';
2+
13
const SELECTORS = {
24
editRow: 'dx-edit-row',
35
deleteRowButton: 'dx-link-delete',
46
undeleteRowButton: 'dx-link-undelete',
57
};
68

7-
export class DataRowModel {
9+
export class DataRowModel extends BaseRowModel {
810
public readonly isEditRow: boolean;
911

10-
constructor(protected readonly root: HTMLElement | null) {
12+
constructor(root: HTMLElement | null) {
13+
super(root);
1114
this.isEditRow = !!this.root?.classList.contains(SELECTORS.editRow);
1215
}
1316

14-
public getElement(): HTMLElement | null {
15-
return this.root;
16-
}
17-
1817
public getDeleteButton(): HTMLElement {
1918
const row = this.getElement() as HTMLElement;
2019

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { FilterCellModel } from '../cell/filter_cell';
2+
import { BaseRowModel } from './base_row';
3+
4+
export class FilterRowModel extends BaseRowModel {
5+
public getFilterCell(columnIndex: number): FilterCellModel {
6+
return new FilterCellModel(this.getCell(columnIndex));
7+
}
8+
}

packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/model/row/group_row.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
1+
import { BaseRowModel } from './base_row';
2+
13
const SELECTORS = {
24
expandCell: 'dx-command-expand',
35
};
46

5-
export class GroupRowModel {
6-
constructor(protected readonly root: HTMLElement | null) {
7-
}
8-
9-
public getElement(): HTMLElement | null {
10-
return this.root;
11-
}
12-
7+
export class GroupRowModel extends BaseRowModel {
138
public getExpandCell(): HTMLElement {
149
const row = this.getElement() as HTMLElement;
1510

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import '@js/ui/tag_box';
2+
3+
import {
4+
afterEach, beforeEach, describe, expect, it,
5+
} from '@jest/globals';
6+
import type { EditorPreparingEvent } from '@js/ui/data_grid';
7+
import { TagBoxModel } from '@ts/ui/__tests__/__mock__/model/tag_box';
8+
9+
import {
10+
afterTest,
11+
beforeTest,
12+
createDataGrid,
13+
flushAsync,
14+
} from '../../__tests__/__mock__/helpers/utils';
15+
16+
describe('FilterRow', () => {
17+
beforeEach(beforeTest);
18+
afterEach(afterTest);
19+
20+
describe('TagBox in filterRow with lookup column (T1306840)', () => {
21+
it('should display tags in TagBox after selecting filter values', async () => {
22+
// arrange
23+
const lookupDataSource = [
24+
{ Value: 0, Text: 'A' },
25+
{ Value: 1, Text: 'B' },
26+
{ Value: 2, Text: 'C' },
27+
];
28+
29+
const { component } = await createDataGrid({
30+
dataSource: [
31+
{ ID: 1, test: 0 },
32+
{ ID: 2, test: 2 },
33+
{ ID: 3, test: 1 },
34+
],
35+
keyExpr: 'ID',
36+
columns: [{
37+
dataField: 'test',
38+
lookup: {
39+
dataSource: lookupDataSource,
40+
displayExpr: 'Text',
41+
valueExpr: 'Value',
42+
},
43+
}],
44+
filterRow: { visible: true },
45+
onEditorPreparing(e: EditorPreparingEvent) {
46+
if (e.parentType === 'filterRow' && e.dataField === 'test') {
47+
e.editorName = 'dxTagBox';
48+
e.editorOptions.dataSource = lookupDataSource;
49+
e.editorOptions.displayExpr = 'Text';
50+
e.editorOptions.valueExpr = 'Value';
51+
e.editorOptions.showSelectionControls = true;
52+
e.editorOptions.applyValueMode = 'useButtons';
53+
}
54+
},
55+
});
56+
57+
await flushAsync();
58+
59+
// act
60+
const filterCell = component.getFilterRow().getFilterCell(0);
61+
const tagBox = filterCell.getEditor(TagBoxModel);
62+
tagBox.setValue([0, 2]);
63+
64+
await flushAsync();
65+
66+
// assert
67+
expect(tagBox.getValue()).toEqual([0, 2]);
68+
expect(tagBox.getTags()).toHaveLength(2);
69+
});
70+
});
71+
});

packages/devextreme/js/__internal/grids/grid_core/filter/m_filter_row.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,22 @@
22
import eventsEngine from '@js/common/core/events/core/events_engine';
33
import { normalizeKeyName } from '@js/common/core/events/utils/index';
44
import messageLocalization from '@js/common/core/localization/message';
5+
import type { dxElementWrapper } from '@js/core/renderer';
56
import $ from '@js/core/renderer';
67
import { equalByValue } from '@js/core/utils/common';
78
import { extend } from '@js/core/utils/extend';
89
import { each, map } from '@js/core/utils/iterator';
910
import { getOuterWidth } from '@js/core/utils/size';
1011
import { isDefined } from '@js/core/utils/type';
11-
import Editor from '@js/ui/editor/editor';
1212
import Menu from '@js/ui/menu';
1313
import Overlay from '@js/ui/overlay/ui.overlay';
1414
import { selectView } from '@js/ui/shared/accessibility';
1515
import type { ColumnsController } from '@ts/grids/grid_core/columns_controller/m_columns_controller';
16+
import Editor from '@ts/ui/editor/editor';
1617
import type MenuInternal from '@ts/ui/menu/menu';
1718

1819
import type { ColumnHeadersView } from '../column_headers/m_column_headers';
20+
import type { Column } from '../columns_controller/types';
1921
import type { ColumnsResizerViewController } from '../columns_resizing_reordering/m_columns_resizing_reordering';
2022
import type { DataController } from '../data_controller/m_data_controller';
2123
import type { EditingController } from '../editing/m_editing';
@@ -76,6 +78,7 @@ const FILTER_MODIFIED_CLASS = 'dx-filter-modified';
7678
const EDITORS_INPUT_SELECTOR = 'input:not([type=\'hidden\'])';
7779

7880
const BETWEEN_OPERATION_DATA_TYPES = ['date', 'datetime', 'number'];
81+
const MULTISELECT_EDITOR_NAMES = ['dxTagBox', 'dxDateRangeBox', 'dxCalendar', 'dxRangeSlider'];
7982

8083
function isOnClickApplyFilterMode(that) {
8184
return that.option('filterRow.applyFilter') === 'onClick';
@@ -124,9 +127,23 @@ const getColumnSelectedFilterOperation = function (that, column) {
124127
}
125128
};
126129

127-
const isValidFilterValue = function (filterValue, column) {
128-
if (column && BETWEEN_OPERATION_DATA_TYPES.includes(column.dataType) && Array.isArray(filterValue)) {
129-
return false;
130+
const hasMultiselectEditor = function ($editorContainer: dxElementWrapper): boolean {
131+
const editor = getEditorInstance($editorContainer);
132+
return !editor || MULTISELECT_EDITOR_NAMES.includes(editor.NAME ?? '');
133+
};
134+
135+
const isValidFilterValue = function (
136+
filterValue: any,
137+
column: Column,
138+
$editorContainer: dxElementWrapper,
139+
): boolean {
140+
if (Array.isArray(filterValue)) {
141+
if (hasMultiselectEditor($editorContainer)) {
142+
return true;
143+
}
144+
if (BETWEEN_OPERATION_DATA_TYPES.includes(column?.dataType ?? '')) {
145+
return false;
146+
}
130147
}
131148

132149
return filterValue !== undefined;
@@ -137,15 +154,20 @@ const getFilterValue = function (that, columnIndex, $editorContainer) {
137154
const filterValue = getColumnFilterValue(that, column);
138155
const isFilterRange = $editorContainer.closest(`.${that.addWidgetPrefix(FILTER_RANGE_OVERLAY_CLASS)}`).length;
139156
const isRangeStart = $editorContainer.hasClass(that.addWidgetPrefix(FILTER_RANGE_START_CLASS));
157+
const isBetween = getColumnSelectedFilterOperation(that, column) === 'between';
140158

141-
if (filterValue && Array.isArray(filterValue) && getColumnSelectedFilterOperation(that, column) === 'between') {
159+
if (filterValue && Array.isArray(filterValue) && isBetween) {
142160
if (isRangeStart) {
143161
return filterValue[0];
144162
}
145163
return filterValue[1];
146164
}
147165

148-
return !isFilterRange && isValidFilterValue(filterValue, column) ? filterValue : null;
166+
if (isFilterRange || !isValidFilterValue(filterValue, column, $editorContainer)) {
167+
return null;
168+
}
169+
170+
return filterValue;
149171
};
150172

151173
const normalizeFilterValue = function (that, filterValue, column, $editorContainer) {
@@ -637,7 +659,6 @@ const columnHeadersView = (Base: ModuleType<ColumnHeadersView>) => class ColumnH
637659

638660
if (!selectedFilterOperation) {
639661
const editor = getEditorInstance($editorContainer);
640-
// @ts-expect-error
641662
if (editor && editor.NAME === 'dxDateBox' && !editor.option('isValid')) {
642663
editor.clear();
643664
editor.option('isValid', true);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type dxTagBox from '@js/ui/tag_box';
2+
import TagBox from '@js/ui/tag_box';
3+
4+
const TAG_CLASS = 'dx-tag';
5+
6+
export class TagBoxModel {
7+
constructor(protected readonly root: HTMLElement) {}
8+
9+
public getInstance(): dxTagBox {
10+
return TagBox.getInstance(this.root) as dxTagBox;
11+
}
12+
13+
public getValue(): (string | number)[] {
14+
return this.getInstance().option('value') as (string | number)[];
15+
}
16+
17+
public setValue(value: (string | number)[]): void {
18+
this.getInstance().option('value', value);
19+
}
20+
21+
public getInput(): HTMLInputElement {
22+
return this.root.querySelector('input') as HTMLInputElement;
23+
}
24+
25+
public getTags(): NodeListOf<HTMLElement> {
26+
return this.root.querySelectorAll(`.${TAG_CLASS}`);
27+
}
28+
}

0 commit comments

Comments
 (0)