Skip to content

Commit 0d8def4

Browse files
authored
CardView - Header Filter: Fix filtering when a custom headerFilter.dataSource is specified as an array of filter expressions (DevExpress#30022)
Co-authored-by: Alyar <>
1 parent 1bf1a87 commit 0d8def4

File tree

3 files changed

+159
-8
lines changed

3 files changed

+159
-8
lines changed

e2e/testcafe-devextreme/tests/cardView/headerFilter/common.functional.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,3 +473,104 @@ test('FilterBuilder should work with custom headerFilter data source', async (t)
473473
filterPanel: { visible: true },
474474
});
475475
});
476+
477+
test('Filtering should work when a custom data source is specified as an array of filter expressions', async (t) => {
478+
// arrange
479+
const cardView = new CardView('#container');
480+
481+
await t
482+
.click(cardView.getHeaderPanel().getHeaderItem(0).getFilterIcon());
483+
484+
// assert
485+
await t
486+
.expect(cardView.getHeaderFilterList().getItems().count)
487+
.eql(2);
488+
489+
// act
490+
await t
491+
.click(cardView.getHeaderFilterList().getItem(0).element)
492+
.click(cardView.getHeaderFilterPopup().getOkButton().element);
493+
494+
// assert
495+
await t
496+
.expect(cardView.getCards().count)
497+
.eql(1)
498+
.expect(cardView.apiGetCombinedFilter())
499+
.eql(['id', '=', 1]);
500+
}).before(async () => {
501+
await createWidget('dxCardView', {
502+
...baseConfig,
503+
columns: [
504+
{
505+
dataField: 'id',
506+
headerFilter: {
507+
dataSource: [
508+
{ value: ['id', '=', 1], text: '1' },
509+
{ value: ['id', '=', 2], text: '2' },
510+
],
511+
},
512+
},
513+
{
514+
dataField: 'title',
515+
},
516+
{
517+
dataField: 'name',
518+
},
519+
{
520+
dataField: 'lastName',
521+
},
522+
],
523+
});
524+
});
525+
526+
test('The item\'s selection state should be correct when a custom data source is specified as an array of filter expressions', async (t) => {
527+
// arrange
528+
const cardView = new CardView('#container');
529+
530+
// assert
531+
await t
532+
.expect(cardView.getCards().count)
533+
.eql(1)
534+
.expect(cardView.apiGetCombinedFilter())
535+
.eql(['id', '=', 1]);
536+
537+
// act
538+
await t
539+
.click(cardView.getHeaderPanel().getHeaderItem(0).getFilterIcon());
540+
541+
const firstHeaderFilterItem = cardView.getHeaderFilterList().getItem(0);
542+
543+
// assert
544+
await t
545+
.expect(cardView.getHeaderFilterList().getItems().count)
546+
.eql(2)
547+
.expect(firstHeaderFilterItem.text)
548+
.eql('1')
549+
.expect(firstHeaderFilterItem.isSelected)
550+
.ok();
551+
}).before(async () => {
552+
await createWidget('dxCardView', {
553+
...baseConfig,
554+
columns: [
555+
{
556+
dataField: 'id',
557+
filterValues: [['id', '=', 1]],
558+
headerFilter: {
559+
dataSource: [
560+
{ value: ['id', '=', 1], text: '1' },
561+
{ value: ['id', '=', 2], text: '2' },
562+
],
563+
},
564+
},
565+
{
566+
dataField: 'title',
567+
},
568+
{
569+
dataField: 'name',
570+
},
571+
{
572+
dataField: 'lastName',
573+
},
574+
],
575+
});
576+
});

packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/utils.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,37 @@ describe('HeaderFilter', () => {
366366
}] as unknown[],
367367
result: [['ID1', 'noneof', [1, 2, 3]], 'and', ['ID2', '<>', 'test1']],
368368
},
369+
{
370+
caseName: 'one column has an array of filter expressions',
371+
columns: [{
372+
...allowFilteringColumnConfig,
373+
dataField: 'ID1',
374+
filterValues: [['ID1', '>', 5], ['ID1', '<', 10]],
375+
}] as unknown[],
376+
result: [[['ID1', '>', 5], 'or', ['ID1', '<', 10]]],
377+
},
378+
{
379+
caseName: 'one column has an array of plain value and filter expressions',
380+
columns: [{
381+
...allowFilteringColumnConfig,
382+
dataField: 'ID1',
383+
filterValues: [5, ['ID1', '=', 10]],
384+
}] as unknown[],
385+
result: [[['ID1', '=', 5], 'or', ['ID1', '=', 10]]],
386+
},
387+
{
388+
caseName: 'two column have an array of filter expressions',
389+
columns: [{
390+
...allowFilteringColumnConfig,
391+
dataField: 'ID1',
392+
filterValues: [['ID1', '>', 5], ['ID1', '<', 10]],
393+
}, {
394+
...allowFilteringColumnConfig,
395+
dataField: 'ID2',
396+
filterValues: [['ID2', '>', 6], ['ID2', '<', 9]],
397+
}] as unknown[],
398+
result: [[['ID1', '>', 5], 'or', ['ID1', '<', 10]], 'and', [['ID2', '>', 6], 'or', ['ID2', '<', 9]]],
399+
},
369400
])('$caseName: should correctly calculate the header filter', ({ columns, result }) => {
370401
const headerFilter = utils.getComposedHeaderFilter(columns as Column[]);
371402

packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/utils.ts

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { FilterType } from '@js/common/grids';
22
import errors from '@js/core/errors';
33
import { isDefined } from '@js/core/utils/type';
4+
import gridCoreUtils from '@ts/grids/grid_core/m_utils';
45
import type { Column } from '@ts/grids/new/grid_core/columns_controller/types';
56

67
import type { FilterValue } from '../types';
@@ -67,22 +68,40 @@ export const needCreateHeaderFilter = (column: Column): boolean => {
6768
return isFilteringAllowed(column) && hasSelectedItems;
6869
};
6970

71+
const getFilterExpression = (filterValues: FilterValue, column: Column): FilterValue => {
72+
const columnName = getColumnName(column);
73+
const hasGroupInterval = !!column.headerFilter?.groupInterval;
74+
75+
const needNormalizeFilterValues = filterValues?.length === 1
76+
&& !hasGroupInterval;
77+
const normalizedFilterValues = needNormalizeFilterValues
78+
? filterValues[0]
79+
: filterValues;
80+
const filterOperator = getFilterOperator(normalizedFilterValues, column.filterType);
81+
82+
return [columnName, filterOperator, normalizedFilterValues];
83+
};
84+
7085
export const getComposedHeaderFilter = (columns: Column[]): FilterValue => {
7186
const filterValue: FilterValue = [];
7287
const filterableColumns = columns.filter((col) => needCreateHeaderFilter(col));
7388

7489
filterableColumns.forEach((column, index) => {
7590
const { filterValues } = column;
76-
const columnName = getColumnName(column);
77-
const hasGroupInterval = !!column.headerFilter?.groupInterval;
91+
const normalizedFilterValues = Array.isArray(filterValues)
92+
? filterValues
93+
: [filterValues];
94+
95+
const filterValuesWithExpressions = normalizedFilterValues
96+
.filter((value) => Array.isArray(value));
97+
const filterValuesWithoutExpressions = normalizedFilterValues
98+
.filter((value) => !Array.isArray(value));
7899

79-
const needNormalizeFilterValues = filterValues?.length === 1 && !hasGroupInterval;
80-
const normalizedFilterValues = needNormalizeFilterValues
81-
? filterValues[0]
82-
: filterValues;
83-
const filterOperator = getFilterOperator(normalizedFilterValues, column.filterType);
100+
const filterExpression = filterValuesWithoutExpressions.length
101+
? [getFilterExpression(filterValuesWithoutExpressions, column)]
102+
: [];
84103

85-
filterValue.push([columnName, filterOperator, normalizedFilterValues]);
104+
filterValue.push(gridCoreUtils.combineFilters([...filterExpression, ...filterValuesWithExpressions], 'or'));
86105

87106
if (index < filterableColumns.length - 1) {
88107
filterValue.push('and');

0 commit comments

Comments
 (0)