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

Commit 0981549

Browse files
Issue 315: Clearable columns (#497)
1 parent 7503d55 commit 0981549

File tree

15 files changed

+536
-117
lines changed

15 files changed

+536
-117
lines changed

CHANGELOG.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,22 @@
22
All notable changes to this project will be documented in this file.
33
This project adheres to [Semantic Versioning](http://semver.org/).
44

5-
#[Unreleased]
5+
## [Unreleased]
6+
### Added
7+
[#497](https://github.com/plotly/dash-table/pull/497)
8+
- New `column.clearable` flag that displays a `Ø` action in the column
9+
Accepts a boolean or array of booleans for multi-line headers.
10+
Clicking a merged column's `Ø` will clear all related columns.
11+
12+
- Clearing column(s) will remove the appropriate data props from each datum
13+
row of `data`.
14+
- Additionally clearing the column will reset the filter for the affected column(s)
15+
16+
### Changed
17+
[#497](https://github.com/plotly/dash-table/pull/497)
18+
- Like for clearing above, deleting through the `x` action will also
19+
reset the filter for the affected column(s)
20+
621
### Fixed
722
[#491](https://github.com/plotly/dash-table/issues/491)
823
- Fixed unconsistent behaviors when editing cell headers

dash-main

Lines changed: 0 additions & 1 deletion
This file was deleted.

demo/AppMode.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import {
1414
import { TooltipSyntax } from 'dash-table/tooltips/props';
1515

1616
export enum AppMode {
17+
Clearable = 'clearable',
18+
ClearableMerged = 'clearableMerged',
1719
Date = 'date',
1820
Default = 'default',
1921
Filtering = 'filtering',
@@ -185,6 +187,24 @@ function getTypedState() {
185187
return state;
186188
}
187189

190+
function getClearableState() {
191+
const state = getDefaultState();
192+
state.tableProps.filter_action = TableAction.Native;
193+
194+
R.forEach(c => {
195+
c.clearable = true;
196+
}, state.tableProps.columns || []);
197+
198+
return state;
199+
}
200+
201+
function getClearableMergedState() {
202+
const state = getClearableState();
203+
state.tableProps.merge_duplicate_headers = true;
204+
205+
return state;
206+
}
207+
188208
function getDateState() {
189209
const state = getTypedState();
190210

@@ -325,6 +345,10 @@ function getState() {
325345
const mode = Environment.searchParams.get('mode');
326346

327347
switch (mode) {
348+
case AppMode.Clearable:
349+
return getClearableState();
350+
case AppMode.ClearableMerged:
351+
return getClearableMergedState();
328352
case AppMode.Date:
329353
return getDateState();
330354
case AppMode.Filtering:

src/dash-table/components/FilterFactory.tsx

Lines changed: 5 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,37 +7,14 @@ 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, IVisibleColumn, VisibleColumns, RowSelection, TableAction } from 'dash-table/components/Table/props';
10+
import { ColumnId, IVisibleColumn, TableAction, IFilterFactoryProps, SetFilter } 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';
14-
import { BasicFilters, Cells, Style } from 'dash-table/derived/style/props';
15-
import { SingleColumnSyntaxTree, getMultiColumnQueryString } from 'dash-table/syntax-tree';
14+
import { SingleColumnSyntaxTree } from 'dash-table/syntax-tree';
1615

1716
import { IEdgesMatrices } from 'dash-table/derived/edges/type';
18-
import { updateMap } from 'dash-table/derived/filter/map';
19-
20-
type SetFilter = (
21-
filter_query: string,
22-
rawFilter: string,
23-
map: Map<string, SingleColumnSyntaxTree>
24-
) => void;
25-
26-
export interface IFilterOptions {
27-
columns: VisibleColumns;
28-
filter_query: string;
29-
filter_action: TableAction;
30-
id: string;
31-
map: Map<string, SingleColumnSyntaxTree>;
32-
rawFilterQuery: string;
33-
row_deletable: boolean;
34-
row_selectable: RowSelection;
35-
setFilter: SetFilter;
36-
style_cell: Style;
37-
style_cell_conditional: Cells;
38-
style_filter: Style;
39-
style_filter_conditional: BasicFilters;
40-
}
17+
import { updateColumnFilter } from 'dash-table/derived/filter/map';
4118

4219
const NO_FILTERS: JSX.Element[][] = [];
4320

@@ -51,7 +28,7 @@ export default class FilterFactory {
5128
return this.propsFn();
5229
}
5330

54-
constructor(private readonly propsFn: () => IFilterOptions) {
31+
constructor(private readonly propsFn: () => IFilterFactoryProps) {
5532

5633
}
5734

@@ -60,17 +37,7 @@ export default class FilterFactory {
6037

6138
const value = ev.target.value.trim();
6239

63-
map = updateMap(map, column, value);
64-
65-
const asts = Array.from(map.values());
66-
const globalFilter = getMultiColumnQueryString(asts);
67-
68-
const rawGlobalFilter = R.map(
69-
ast => ast.query || '',
70-
R.filter<SingleColumnSyntaxTree>(ast => Boolean(ast), asts)
71-
).join(' && ');
72-
73-
setFilter(globalFilter, rawGlobalFilter, map);
40+
updateColumnFilter(map, column, value, setFilter);
7441
}
7542

7643
private filter = memoizerCache<[ColumnId, number]>()((

src/dash-table/components/HeaderFactory.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import React, { CSSProperties } from 'react';
44
import { arrayMap2 } from 'core/math/arrayZipMap';
55
import { matrixMap2, matrixMap3 } from 'core/math/matrixZipMap';
66

7-
import { ControlledTableProps, VisibleColumns } from 'dash-table/components/Table/props';
7+
import { VisibleColumns, HeaderFactoryProps } from 'dash-table/components/Table/props';
88
import derivedHeaderContent from 'dash-table/derived/header/content';
99
import getHeaderRows from 'dash-table/derived/header/headerRows';
1010
import getIndices from 'dash-table/derived/header/indices';
@@ -29,7 +29,7 @@ export default class HeaderFactory {
2929
return this.propsFn();
3030
}
3131

32-
constructor(private readonly propsFn: () => ControlledTableProps) {
32+
constructor(private readonly propsFn: () => HeaderFactoryProps) {
3333

3434
}
3535

@@ -39,10 +39,12 @@ export default class HeaderFactory {
3939
const {
4040
columns,
4141
data,
42+
map,
4243
merge_duplicate_headers,
4344
page_action,
4445
row_deletable,
4546
row_selectable,
47+
setFilter,
4648
setProps,
4749
sort_action,
4850
sort_by,
@@ -90,12 +92,15 @@ export default class HeaderFactory {
9092

9193
const contents = this.headerContent(
9294
columns,
95+
merge_duplicate_headers,
9396
data,
9497
labelsAndIndices,
98+
map,
9599
sort_action,
96100
sort_mode,
97101
sort_by,
98102
page_action,
103+
setFilter,
99104
setProps,
100105
merge_duplicate_headers
101106
);

src/dash-table/components/Table/Table.less

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -332,8 +332,9 @@
332332
}
333333

334334
th {
335-
.column-header--edit,
335+
.column-header--clear,
336336
.column-header--delete,
337+
.column-header--edit,
337338
.sort {
338339
.not-selectable();
339340
cursor: pointer;
@@ -483,29 +484,36 @@
483484
color: var(--accent);
484485
}
485486

486-
.dash-spreadsheet-inner .column-header--edit {
487-
float: left;
488-
opacity: 0.1;
489-
padding-left: 2px;
490-
padding-right: 2px;
491-
cursor: pointer;
492-
}
487+
.dash-spreadsheet-inner {
488+
.column-header--clear::before {
489+
content: 'ø';
490+
}
493491

494-
.dash-spreadsheet-inner th:hover .column-header--edit {
495-
color: var(--accent);
496-
opacity: 1;
497-
}
492+
.column-header--delete::before {
493+
content: '×'
494+
}
498495

499-
.dash-spreadsheet-inner .column-header--delete {
500-
float: left;
501-
opacity: 0.1;
502-
padding-left: 2px;
503-
padding-right: 2px;
504-
cursor: pointer;
505-
}
496+
.column-header--edit::before {
497+
content: '';
498+
}
506499

507-
.dash-spreadsheet-inner th:hover .column-header--delete {
508-
color: var(--accent);
509-
opacity: 1;
500+
.column-header--clear,
501+
.column-header--delete,
502+
.column-header--edit {
503+
float: left;
504+
opacity: 0.1;
505+
padding-left: 2px;
506+
padding-right: 2px;
507+
cursor: pointer;
508+
}
509+
510+
th:hover {
511+
.column-header--clear,
512+
.column-header--delete,
513+
.column-header--edit {
514+
color: var(--accent);
515+
opacity: 1;
516+
}
517+
}
510518
}
511519
}

src/dash-table/components/Table/props.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ export interface IDatetimeColumn extends ITypeColumn {
154154
}
155155

156156
export interface IBaseVisibleColumn {
157+
clearable?: boolean | boolean[];
157158
deletable?: boolean | boolean[];
158159
editable: boolean;
159160
renamable?: boolean | boolean[];
@@ -393,6 +394,33 @@ export type ControlledTableProps = SanitizedProps & IState & {
393394
virtualized: IVirtualizedDerivedData;
394395
};
395396

397+
export type SetFilter = (
398+
filter_query: string,
399+
rawFilter: string,
400+
map: Map<string, SingleColumnSyntaxTree>
401+
) => void;
402+
403+
export interface IFilterFactoryProps {
404+
columns: VisibleColumns;
405+
filter_query: string;
406+
filter_action: TableAction;
407+
id: string;
408+
map: Map<string, SingleColumnSyntaxTree>;
409+
rawFilterQuery: string;
410+
row_deletable: boolean;
411+
row_selectable: RowSelection;
412+
setFilter: SetFilter;
413+
style_cell: Style;
414+
style_cell_conditional: Cells;
415+
style_filter: Style;
416+
style_filter_conditional: BasicFilters;
417+
}
418+
419+
export type HeaderFactoryProps = ControlledTableProps & {
420+
map: Map<string, SingleColumnSyntaxTree>;
421+
setFilter: SetFilter;
422+
};
423+
396424
export interface ICellFactoryProps {
397425
active_cell: ICellCoordinates;
398426
columns: VisibleColumns;

src/dash-table/dash/DataTable.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,25 @@ export const propTypes = {
117117
*/
118118
columns: PropTypes.arrayOf(PropTypes.exact({
119119

120+
/**
121+
* If True, the user can clear the column by clicking on a little `Ø`
122+
* button on the column.
123+
* If there are merged, multi-header columns then you can choose
124+
* which column header row to display the "Ø" in by
125+
* supplying an array of booleans.
126+
* For example, `[true, false]` will display the "Ø" on the first row,
127+
* but not the second row.
128+
* If the "Ø" appears on a merged column, then clicking on that button
129+
* will clear *all* of the merged columns associated with it.
130+
*
131+
* Unlike `column.deletable`, this action does not remove the column(s)
132+
* from the table. It only removed the associated entries from `data`.
133+
*/
134+
clearable: PropTypes.oneOfType([
135+
PropTypes.bool,
136+
PropTypes.arrayOf(PropTypes.bool)
137+
]),
138+
120139
/**
121140
* If True, the user can delete the column by clicking on a little `x`
122141
* button on the column.

src/dash-table/derived/filter/map.ts

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import * as R from 'ramda';
22

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

5-
import { VisibleColumns, IVisibleColumn } from 'dash-table/components/Table/props';
6-
import { SingleColumnSyntaxTree, MultiColumnsSyntaxTree, getSingleColumnMap } from 'dash-table/syntax-tree';
5+
import { VisibleColumns, IVisibleColumn, SetFilter } from 'dash-table/components/Table/props';
6+
import { SingleColumnSyntaxTree, MultiColumnsSyntaxTree, getMultiColumnQueryString, getSingleColumnMap } from 'dash-table/syntax-tree';
77

88
const cloneIf = (
99
current: Map<string, SingleColumnSyntaxTree>,
@@ -61,20 +61,49 @@ export default memoizeOneFactory((
6161
return newMap;
6262
});
6363

64-
export const updateMap = (
65-
map: Map<string, SingleColumnSyntaxTree>,
66-
column: IVisibleColumn,
67-
value: any
68-
): Map<string, SingleColumnSyntaxTree> => {
69-
const safeColumnId = column.id.toString();
70-
64+
function updateMap(map: Map<string, SingleColumnSyntaxTree>, column: IVisibleColumn, value: any) {
65+
const id = column.id.toString();
7166
const newMap = new Map<string, SingleColumnSyntaxTree>(map);
7267

7368
if (value && value.length) {
74-
newMap.set(safeColumnId, new SingleColumnSyntaxTree(value, column));
69+
newMap.set(id, new SingleColumnSyntaxTree(value, column));
7570
} else {
76-
newMap.delete(safeColumnId);
71+
newMap.delete(id);
7772
}
7873

7974
return newMap;
75+
}
76+
77+
function updateState(map: Map<string, SingleColumnSyntaxTree>, setFilter: SetFilter) {
78+
const asts = Array.from(map.values());
79+
const globalFilter = getMultiColumnQueryString(asts);
80+
81+
const rawGlobalFilter = R.map(
82+
ast => ast.query || '',
83+
R.filter<SingleColumnSyntaxTree>(ast => Boolean(ast), asts)
84+
).join(' && ');
85+
86+
setFilter(globalFilter, rawGlobalFilter, map);
87+
}
88+
89+
export const updateColumnFilter = (
90+
map: Map<string, SingleColumnSyntaxTree>,
91+
column: IVisibleColumn,
92+
value: any,
93+
setFilter: SetFilter
94+
) => {
95+
map = updateMap(map, column, value);
96+
updateState(map, setFilter);
97+
};
98+
99+
export const clearColumnsFilter = (
100+
map: Map<string, SingleColumnSyntaxTree>,
101+
columns: VisibleColumns,
102+
setFilter: SetFilter
103+
) => {
104+
R.forEach(column => {
105+
map = updateMap(map, column, '');
106+
}, columns);
107+
108+
updateState(map, setFilter);
80109
};

0 commit comments

Comments
 (0)