Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const SELECTORS = {
editorCell: 'dx-editor-cell',
editorContainer: 'dx-editor-container',
widget: 'dx-widget',
};

export class FilterCellModel {
constructor(protected readonly root: HTMLElement | null) {}

public getElement(): HTMLElement | null {
return this.root;
}

public getEditor<T>(EditorModel: new (element: HTMLElement) => T): T {
const editorElement = this.root?.querySelector(`.${SELECTORS.editorContainer} .${SELECTORS.widget}`) as HTMLElement;
return new EditorModel(editorElement);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ import { ConfirmationDialogModel } from './confirmation_dialog';
import { EditFormModel } from './edit_form';
import { FilterPanelModel } from './filter_panel';
import { DataRowModel } from './row/data_row';
import { FilterRowModel } from './row/filter_row';
import { GroupRowModel } from './row/group_row';

const SELECTORS = {
headerRowClass: 'dx-header-row',
dataRowClass: 'dx-data-row',
groupRowClass: 'dx-group-row',
filterRowClass: 'filter-row',
scrollableContainer: 'dx-scrollable-container',
loadPanel: 'dx-loadpanel',
editForm: 'edit-form',
Expand Down Expand Up @@ -90,6 +92,13 @@ export abstract class GridCoreModel<TInstance = GridBase | CardView> {
);
}

public getFilterRow(): FilterRowModel {
const filterRowElement = this.root.querySelector(
`.${this.addWidgetPrefix(SELECTORS.filterRowClass)}`,
);
return new FilterRowModel(filterRowElement as HTMLElement);
}

public getScrollableContainer(): HTMLElement {
return this.root.querySelector(`.${SELECTORS.scrollableContainer}`) as HTMLElement;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export class BaseRowModel {
constructor(protected readonly root: HTMLElement | null) {}

public getElement(): HTMLElement | null {
return this.root;
}

public getCells(): NodeListOf<HTMLElement> {
return this.root?.querySelectorAll('td') as NodeListOf<HTMLElement>;
}

public getCell(columnIndex: number): HTMLElement | null {
return this.getCells()?.[columnIndex] ?? null;
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import { BaseRowModel } from './base_row';

const SELECTORS = {
editRow: 'dx-edit-row',
deleteRowButton: 'dx-link-delete',
undeleteRowButton: 'dx-link-undelete',
};

export class DataRowModel {
export class DataRowModel extends BaseRowModel {
public readonly isEditRow: boolean;

constructor(protected readonly root: HTMLElement | null) {
constructor(root: HTMLElement | null) {
super(root);
this.isEditRow = !!this.root?.classList.contains(SELECTORS.editRow);
}

public getElement(): HTMLElement | null {
return this.root;
}

public getDeleteButton(): HTMLElement {
const row = this.getElement() as HTMLElement;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { FilterCellModel } from '../cell/filter_cell';
import { BaseRowModel } from './base_row';

export class FilterRowModel extends BaseRowModel {
public getFilterCell(columnIndex: number): FilterCellModel {
return new FilterCellModel(this.getCell(columnIndex));
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import { BaseRowModel } from './base_row';

const SELECTORS = {
expandCell: 'dx-command-expand',
};

export class GroupRowModel {
constructor(protected readonly root: HTMLElement | null) {
}

public getElement(): HTMLElement | null {
return this.root;
}

export class GroupRowModel extends BaseRowModel {
public getExpandCell(): HTMLElement {
const row = this.getElement() as HTMLElement;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import '@js/ui/tag_box';

import {
afterEach, beforeEach, describe, expect, it,
} from '@jest/globals';
import type { EditorPreparingEvent } from '@js/ui/data_grid';
import { TagBoxModel } from '@ts/ui/__tests__/__mock__/model/tag_box';

import {
afterTest,
beforeTest,
createDataGrid,
flushAsync,
} from '../../__tests__/__mock__/helpers/utils';

describe('FilterRow', () => {
beforeEach(beforeTest);
afterEach(afterTest);

describe('TagBox in filterRow with lookup column (T1306840)', () => {
it('should display tags in TagBox after selecting filter values', async () => {
// arrange
const lookupDataSource = [
{ Value: 0, Text: 'A' },
{ Value: 1, Text: 'B' },
{ Value: 2, Text: 'C' },
];

const { component } = await createDataGrid({
dataSource: [
{ ID: 1, test: 0 },
{ ID: 2, test: 2 },
{ ID: 3, test: 1 },
],
keyExpr: 'ID',
columns: [{
dataField: 'test',
lookup: {
dataSource: lookupDataSource,
displayExpr: 'Text',
valueExpr: 'Value',
},
}],
filterRow: { visible: true },
onEditorPreparing(e: EditorPreparingEvent) {
if (e.parentType === 'filterRow' && e.dataField === 'test') {
e.editorName = 'dxTagBox';
e.editorOptions.dataSource = lookupDataSource;
e.editorOptions.displayExpr = 'Text';
e.editorOptions.valueExpr = 'Value';
e.editorOptions.showSelectionControls = true;
e.editorOptions.applyValueMode = 'useButtons';
}
},
});

await flushAsync();

// act
const filterCell = component.getFilterRow().getFilterCell(0);
const tagBox = filterCell.getEditor(TagBoxModel);
tagBox.setValue([0, 2]);

await flushAsync();

// assert
expect(tagBox.getValue()).toEqual([0, 2]);
expect(tagBox.getTags()).toHaveLength(2);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,22 @@
import eventsEngine from '@js/common/core/events/core/events_engine';
import { normalizeKeyName } from '@js/common/core/events/utils/index';
import messageLocalization from '@js/common/core/localization/message';
import type { dxElementWrapper } from '@js/core/renderer';
import $ from '@js/core/renderer';
import { equalByValue } from '@js/core/utils/common';
import { extend } from '@js/core/utils/extend';
import { each, map } from '@js/core/utils/iterator';
import { getOuterWidth } from '@js/core/utils/size';
import { isDefined } from '@js/core/utils/type';
import Editor from '@js/ui/editor/editor';
import Menu from '@js/ui/menu';
import Overlay from '@js/ui/overlay/ui.overlay';
import { selectView } from '@js/ui/shared/accessibility';
import type { ColumnsController } from '@ts/grids/grid_core/columns_controller/m_columns_controller';
import Editor from '@ts/ui/editor/editor';
import type MenuInternal from '@ts/ui/menu/menu';

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

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

function isOnClickApplyFilterMode(that) {
return that.option('filterRow.applyFilter') === 'onClick';
Expand Down Expand Up @@ -124,9 +127,23 @@ const getColumnSelectedFilterOperation = function (that, column) {
}
};

const isValidFilterValue = function (filterValue, column) {
if (column && BETWEEN_OPERATION_DATA_TYPES.includes(column.dataType) && Array.isArray(filterValue)) {
return false;
const hasMultiselectEditor = function ($editorContainer: dxElementWrapper): boolean {
const editor = getEditorInstance($editorContainer);
return !editor || MULTISELECT_EDITOR_NAMES.includes(editor.NAME ?? '');
};

const isValidFilterValue = function (
filterValue: any,
column: Column,
$editorContainer: dxElementWrapper,
): boolean {
if (Array.isArray(filterValue)) {
if (hasMultiselectEditor($editorContainer)) {
return true;
}
if (BETWEEN_OPERATION_DATA_TYPES.includes(column?.dataType ?? '')) {
return false;
}
}

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

if (filterValue && Array.isArray(filterValue) && getColumnSelectedFilterOperation(that, column) === 'between') {
if (filterValue && Array.isArray(filterValue) && isBetween) {
if (isRangeStart) {
return filterValue[0];
}
return filterValue[1];
}

return !isFilterRange && isValidFilterValue(filterValue, column) ? filterValue : null;
if (isFilterRange || !isValidFilterValue(filterValue, column, $editorContainer)) {
return null;
}

return filterValue;
};

const normalizeFilterValue = function (that, filterValue, column, $editorContainer) {
Expand Down Expand Up @@ -639,7 +661,6 @@ const columnHeadersView = (Base: ModuleType<ColumnHeadersView>) => class ColumnH

if (!selectedFilterOperation) {
const editor = getEditorInstance($editorContainer);
// @ts-expect-error
if (editor && editor.NAME === 'dxDateBox' && !editor.option('isValid')) {
editor.clear();
editor.option('isValid', true);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type dxTagBox from '@js/ui/tag_box';
import TagBox from '@js/ui/tag_box';

const TAG_CLASS = 'dx-tag';

export class TagBoxModel {
constructor(protected readonly root: HTMLElement) {}

public getInstance(): dxTagBox {
return TagBox.getInstance(this.root) as dxTagBox;
}

public getValue(): (string | number)[] {
return this.getInstance().option('value') as (string | number)[];
}

public setValue(value: (string | number)[]): void {
this.getInstance().option('value', value);
}

public getInput(): HTMLInputElement {
return this.root.querySelector('input') as HTMLInputElement;
}

public getTags(): NodeListOf<HTMLElement> {
return this.root.querySelectorAll(`.${TAG_CLASS}`);
}
}
Loading