Skip to content
This repository was archived by the owner on Jun 4, 2024. It is now read-only.

Commit 059103a

Browse files
Issue 564 - Customizable column filtering logical operator (#751)
1 parent 3ee8381 commit 059103a

File tree

17 files changed

+246
-81
lines changed

17 files changed

+246
-81
lines changed

Diff for: src/dash-table/components/ControlledTable/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -774,7 +774,7 @@ export default class ControlledTable extends PureComponent<ControlledTableProps>
774774
...(empty[1][1] ? ['dash-empty-11'] : []),
775775
...(visibleColumns.length ? [] : ['dash-no-columns']),
776776
...(virtualized.data.length ? [] : ['dash-no-data']),
777-
...(filter_action !== TableAction.None ? [] : ['dash-no-filter']),
777+
...(filter_action.type !== TableAction.None ? [] : ['dash-no-filter']),
778778
...(fill_width ? ['dash-fill-width'] : []),
779779
...(loading_state ? ['dash-loading'] : [])
780780
];

Diff for: src/dash-table/components/EdgeFactory.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ export default class EdgeFactory {
164164
columns,
165165
visibleColumns,
166166
(row_deletable ? 1 : 0) + (row_selectable ? 1 : 0),
167-
filter_action !== TableAction.None,
167+
filter_action.type !== TableAction.None,
168168
workFilter.map,
169169
fixed_columns,
170170
fixed_rows,

Diff for: src/dash-table/components/FilterFactory.tsx

+12-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import memoizerCache from 'core/cache/memoizer';
77
import { memoizeOne } from 'core/memoizer';
88

99
import ColumnFilter from 'dash-table/components/Filter/Column';
10-
import { ColumnId, IColumn, TableAction, IFilterFactoryProps, SetFilter } from 'dash-table/components/Table/props';
10+
import { ColumnId, IColumn, TableAction, IFilterFactoryProps, SetFilter, FilterLogicalOperator } from 'dash-table/components/Table/props';
1111
import derivedFilterStyles, { derivedFilterOpStyles } from 'dash-table/derived/filter/wrapperStyles';
1212
import derivedHeaderOperations from 'dash-table/derived/header/operations';
1313
import { derivedRelevantFilterStyles } from 'dash-table/derived/style';
@@ -32,18 +32,24 @@ export default class FilterFactory {
3232

3333
}
3434

35-
private onChange = (column: IColumn, map: Map<string, SingleColumnSyntaxTree>, setFilter: SetFilter, ev: any) => {
35+
private onChange = (
36+
column: IColumn,
37+
map: Map<string, SingleColumnSyntaxTree>,
38+
operator: FilterLogicalOperator,
39+
setFilter: SetFilter, ev: any
40+
) => {
3641
Logger.debug('Filter -- onChange', column.id, ev.target.value && ev.target.value.trim());
3742

3843
const value = ev.target.value.trim();
3944

40-
updateColumnFilter(map, column, value, setFilter);
45+
updateColumnFilter(map, column, operator, value, setFilter);
4146
}
4247

4348
private filter = memoizerCache<[ColumnId, number]>()((
4449
column: IColumn,
4550
index: number,
4651
map: Map<string, SingleColumnSyntaxTree>,
52+
operator: FilterLogicalOperator,
4753
setFilter: SetFilter
4854
) => {
4955
const ast = map.get(column.id.toString());
@@ -53,7 +59,7 @@ export default class FilterFactory {
5359
className={`dash-filter column-${index}`}
5460
columnId={column.id}
5561
isValid={!ast || ast.isValid}
56-
setFilter={this.onChange.bind(this, column, map, setFilter)}
62+
setFilter={this.onChange.bind(this, column, map, operator, setFilter)}
5763
value={ast && ast.query}
5864
/>);
5965
});
@@ -86,7 +92,7 @@ export default class FilterFactory {
8692
visibleColumns
8793
} = this.props;
8894

89-
if (filter_action === TableAction.None) {
95+
if (filter_action.type === TableAction.None) {
9096
return NO_FILTERS;
9197
}
9298

@@ -113,6 +119,7 @@ export default class FilterFactory {
113119
column,
114120
index,
115121
map,
122+
filter_action.operator,
116123
setFilter
117124
);
118125
}, visibleColumns);

Diff for: src/dash-table/components/HeaderFactory.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export default class HeaderFactory {
3939
column_selectable,
4040
columns,
4141
data,
42+
filter_action,
4243
hidden_columns,
4344
id,
4445
map,
@@ -106,6 +107,7 @@ export default class HeaderFactory {
106107
sort_action,
107108
sort_mode,
108109
sort_by,
110+
filter_action.operator,
109111
page_action,
110112
setFilter,
111113
setProps,

Diff for: src/dash-table/components/Table/derivedPropsHelper.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export default () => {
5454
const invalidatedSort = sortCache(sort_by);
5555

5656
const invalidateSelection =
57-
(!invalidatedFilter.cached && !invalidatedFilter.first && filter_action === TableAction.Custom) ||
57+
(!invalidatedFilter.cached && !invalidatedFilter.first && filter_action.type === TableAction.Custom) ||
5858
(!invalidatedPagination.cached && !invalidatedPagination.first && page_action === TableAction.Custom) ||
5959
(!invalidatedSort.cached && !invalidatedSort.first && sort_action === TableAction.Custom);
6060

Diff for: src/dash-table/components/Table/index.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export default class Table extends Component<SanitizedAndDerivedProps, Standalon
4040
value: props.filter_query,
4141
map: this.filterMap(
4242
new Map<string, SingleColumnSyntaxTree>(),
43+
props.filter_action.operator,
4344
props.filter_query,
4445
props.visibleColumns
4546
)
@@ -60,6 +61,7 @@ export default class Table extends Component<SanitizedAndDerivedProps, Standalon
6061
if (value !== nextProps.filter_query) {
6162
const map = this.filterMap(
6263
currentMap,
64+
nextProps.filter_action.operator,
6365
nextProps.filter_query,
6466
nextProps.visibleColumns
6567
);

Diff for: src/dash-table/components/Table/props.ts

+13-2
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,16 @@ export interface IDerivedData {
5757
indices: Indices;
5858
}
5959

60+
export enum FilterLogicalOperator {
61+
And = 'and',
62+
Or = 'or'
63+
}
64+
65+
export interface IFilterAction {
66+
type: TableAction;
67+
operator: FilterLogicalOperator;
68+
}
69+
6070
export interface IViewportOffset {
6171
rows: number;
6272
columns: number;
@@ -299,7 +309,7 @@ export interface IProps {
299309
editable?: boolean;
300310
fill_width?: boolean;
301311
filter_query?: string;
302-
filter_action?: TableAction;
312+
filter_action?: TableAction | IFilterAction;
303313
hidden_columns?: string[];
304314
include_headers_on_copy_paste?: boolean;
305315
locale_format: INumberLocale;
@@ -410,6 +420,7 @@ export type SanitizedProps = Omit<Omit<
410420
Merge<PropsWithDefaults, {
411421
columns: Columns;
412422
data: Data;
423+
filter_action: IFilterAction;
413424
fixed_columns: number;
414425
fixed_rows: number;
415426
loading_state: boolean;
@@ -442,7 +453,7 @@ export type SetFilter = (
442453

443454
export interface IFilterFactoryProps {
444455
filter_query: string;
445-
filter_action: TableAction;
456+
filter_action: IFilterAction;
446457
id: string;
447458
map: Map<string, SingleColumnSyntaxTree>;
448459
rawFilterQuery: string;

Diff for: src/dash-table/dash/DataTable.js

+17-1
Original file line numberDiff line numberDiff line change
@@ -964,7 +964,23 @@ export const propTypes = {
964964
* through a callback (where `filter_query` or `derived_filter_query_structure` would be the input
965965
* and `data` would be the output).
966966
*/
967-
filter_action: PropTypes.oneOf(['custom', 'native', 'none']),
967+
filter_action: PropTypes.oneOfType([
968+
PropTypes.oneOf([
969+
'custom',
970+
'native',
971+
'none'
972+
]),
973+
PropTypes.shape({
974+
type: PropTypes.oneOf([
975+
'custom',
976+
'native'
977+
]).isRequired,
978+
operator: PropTypes.oneOf([
979+
'and',
980+
'or'
981+
])
982+
})
983+
]),
968984

969985
/**
970986
* The `sort_action` property enables data to be

Diff for: src/dash-table/dash/Sanitizer.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ import {
1414
SortAsNull,
1515
TableAction,
1616
ExportFormat,
17-
ExportHeaders
17+
ExportHeaders,
18+
IFilterAction,
19+
FilterLogicalOperator
1820
} from 'dash-table/components/Table/props';
1921
import headerRows from 'dash-table/derived/header/headerRows';
2022
import resolveFlag from 'dash-table/derived/cell/resolveFlag';
@@ -70,6 +72,12 @@ const applyDefaultsToColumns = (
7072

7173
const applyDefaultToLocale = (locale: INumberLocale) => getLocale(locale);
7274

75+
const getFilterAction = (
76+
action: TableAction | IFilterAction
77+
): IFilterAction => typeof action === 'object' ?
78+
{ type: action.type ?? TableAction.None, operator: action.operator ?? FilterLogicalOperator.And } :
79+
{ type: action, operator: FilterLogicalOperator.And };
80+
7381
const getVisibleColumns = (
7482
columns: Columns,
7583
hiddenColumns: string[] | undefined
@@ -95,6 +103,7 @@ export default class Sanitizer {
95103
columns,
96104
data,
97105
export_headers: headerFormat,
106+
filter_action: this.getFilterAction(props.filter_action),
98107
fixed_columns: getFixedColumns(props.fixed_columns, props.row_deletable, props.row_selectable),
99108
fixed_rows: getFixedRows(props.fixed_rows, columns, props.filter_action),
100109
loading_state: dataLoading(props.loading_state),
@@ -107,6 +116,7 @@ export default class Sanitizer {
107116

108117
private readonly applyDefaultsToColumns = memoizeOne(applyDefaultsToColumns);
109118

119+
private readonly getFilterAction = memoizeOne(getFilterAction);
110120
private readonly getVisibleColumns = memoizeOne(getVisibleColumns);
111121
}
112122

Diff for: src/dash-table/derived/data/virtual.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ import {
99
IDerivedData,
1010
SortAsNull,
1111
Columns,
12-
TableAction
12+
TableAction,
13+
IFilterAction
1314
} from 'dash-table/components/Table/props';
1415
import { QuerySyntaxTree } from 'dash-table/syntax-tree';
1516

1617
const getter = (
1718
columns: Columns,
1819
data: Data,
19-
filter_action: TableAction,
20+
filter_action: IFilterAction,
2021
filter_query: string,
2122
sort_action: TableAction,
2223
sort_by: SortBy = []
@@ -26,7 +27,7 @@ const getter = (
2627
map.set(datum, index);
2728
}, data);
2829

29-
if (filter_action === TableAction.Native) {
30+
if (filter_action.type === TableAction.Native) {
3031
const tree = new QuerySyntaxTree(filter_query);
3132

3233
data = tree.isValid ?

Diff for: src/dash-table/derived/filter/map.ts

+15-9
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as R from 'ramda';
22

33
import { memoizeOneFactory } from 'core/memoizer';
44

5-
import { Columns, IColumn, SetFilter } from 'dash-table/components/Table/props';
5+
import { Columns, IColumn, SetFilter, FilterLogicalOperator } from 'dash-table/components/Table/props';
66
import { SingleColumnSyntaxTree, MultiColumnsSyntaxTree, getMultiColumnQueryString, getSingleColumnMap } from 'dash-table/syntax-tree';
77

88
const cloneIf = (
@@ -12,12 +12,12 @@ const cloneIf = (
1212

1313
export default memoizeOneFactory((
1414
map: Map<string, SingleColumnSyntaxTree>,
15+
operator: FilterLogicalOperator,
1516
query: string,
1617
columns: Columns
1718
): Map<string, SingleColumnSyntaxTree> => {
18-
const multiQuery = new MultiColumnsSyntaxTree(query);
19+
const multiQuery = new MultiColumnsSyntaxTree(query, operator);
1920
const reversedMap = getSingleColumnMap(multiQuery, columns);
20-
2121
/*
2222
* Couldn't process the query, just use the previous value.
2323
*/
@@ -74,36 +74,42 @@ function updateMap(map: Map<string, SingleColumnSyntaxTree>, column: IColumn, va
7474
return newMap;
7575
}
7676

77-
function updateState(map: Map<string, SingleColumnSyntaxTree>, setFilter: SetFilter) {
77+
function updateState(
78+
map: Map<string, SingleColumnSyntaxTree>,
79+
operator: FilterLogicalOperator,
80+
setFilter: SetFilter
81+
) {
7882
const asts = Array.from(map.values());
79-
const globalFilter = getMultiColumnQueryString(asts);
83+
const globalFilter = getMultiColumnQueryString(asts, operator);
8084

8185
const rawGlobalFilter = R.map(
8286
ast => ast.query || '',
8387
R.filter<SingleColumnSyntaxTree>(ast => Boolean(ast), asts)
84-
).join(' && ');
88+
).join(operator === FilterLogicalOperator.And ? ' && ' : ' || ');
8589

8690
setFilter(globalFilter, rawGlobalFilter, map);
8791
}
8892

8993
export const updateColumnFilter = (
9094
map: Map<string, SingleColumnSyntaxTree>,
9195
column: IColumn,
96+
operator: FilterLogicalOperator,
9297
value: any,
9398
setFilter: SetFilter
9499
) => {
95100
map = updateMap(map, column, value);
96-
updateState(map, setFilter);
101+
updateState(map, operator, setFilter);
97102
};
98103

99104
export const clearColumnsFilter = (
100105
map: Map<string, SingleColumnSyntaxTree>,
101106
columns: Columns,
107+
operator: FilterLogicalOperator,
102108
setFilter: SetFilter
103109
) => {
104110
R.forEach(column => {
105111
map = updateMap(map, column, '');
106112
}, columns);
107113

108-
updateState(map, setFilter);
109-
};
114+
updateState(map, operator, setFilter);
115+
};

Diff for: src/dash-table/derived/header/content.tsx

+7-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import {
1515
SetFilter,
1616
SetProps,
1717
SortMode,
18-
TableAction
18+
TableAction,
19+
FilterLogicalOperator
1920
} from 'dash-table/components/Table/props';
2021
import getColumnFlag from 'dash-table/derived/header/columnFlag';
2122
import * as actions from 'dash-table/utils/actions';
@@ -34,6 +35,7 @@ const doAction = (
3435
selected_columns: string[],
3536
column: IColumn,
3637
columns: Columns,
38+
operator: FilterLogicalOperator,
3739
visibleColumns: Columns,
3840
columnRowIndex: any,
3941
mergeDuplicateHeaders: boolean,
@@ -64,7 +66,7 @@ const doAction = (
6466
}
6567
}, affectedColumIds);
6668

67-
clearColumnsFilter(map, affectedColumns, setFilter);
69+
clearColumnsFilter(map, affectedColumns, operator, setFilter);
6870
};
6971

7072
function doSort(columnId: ColumnId, sortBy: SortBy, mode: SortMode, setProps: SetProps) {
@@ -162,6 +164,7 @@ function getter(
162164
sort_action: TableAction,
163165
mode: SortMode,
164166
sortBy: SortBy,
167+
filterOperator: FilterLogicalOperator,
165168
paginationMode: TableAction,
166169
setFilter: SetFilter,
167170
setProps: SetProps,
@@ -263,7 +266,7 @@ function getter(
263266
null :
264267
(<span
265268
className='column-header--clear'
266-
onClick={doAction(actions.clearColumn, selected_columns, column, columns, visibleColumns, headerRowIndex, mergeDuplicateHeaders, setFilter, setProps, map, data)}
269+
onClick={doAction(actions.clearColumn, selected_columns, column, columns, filterOperator, visibleColumns, headerRowIndex, mergeDuplicateHeaders, setFilter, setProps, map, data)}
267270
>
268271
<FontAwesomeIcon icon='eraser' />
269272
</span>)
@@ -275,7 +278,7 @@ function getter(
275278
className={'column-header--delete' + (spansAllColumns ? ' disabled' : '')}
276279
onClick={spansAllColumns ?
277280
undefined :
278-
doAction(actions.deleteColumn, selected_columns, column, columns, visibleColumns, headerRowIndex, mergeDuplicateHeaders, setFilter, setProps, map, data)
281+
doAction(actions.deleteColumn, selected_columns, column, columns, filterOperator, visibleColumns, headerRowIndex, mergeDuplicateHeaders, setFilter, setProps, map, data)
279282
}
280283
>
281284
<FontAwesomeIcon icon={['far', 'trash-alt']} />

Diff for: src/dash-table/syntax-tree/MultiColumnsSyntaxTree.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ import { ISyntaxTree } from 'core/syntax-tree/syntaxer';
66

77
import columnMultiLexicon from './lexicon/columnMulti';
88
import { ILexemeResult } from 'core/syntax-tree/lexer';
9+
import { FilterLogicalOperator } from 'dash-table/components/Table/props';
910

1011
export default class MultiColumnsSyntaxTree extends SyntaxTree {
11-
constructor(query: string) {
12-
super(columnMultiLexicon, query);
12+
constructor(query: string, operator: FilterLogicalOperator) {
13+
super(columnMultiLexicon(operator), query);
1314
}
1415
get isValid() {
1516
return super.isValid &&

0 commit comments

Comments
 (0)