Skip to content

Commit d4879a1

Browse files
author
Shammamah Hossain
committed
Merge pull request plotly#484 from plotly/loading-states-uneditable
Add loading states and prevent cells from being edited when table is loading.
1 parent 7f14573 commit d4879a1

File tree

19 files changed

+690
-73
lines changed

19 files changed

+690
-73
lines changed

Diff for: packages/dash-table/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).
1414
[#18](https://github.com/plotly/dash-table/issues/18)
1515
- Fix row selection vertical and horizontal alignment
1616

17+
### Added
18+
[#319](https://github.com/plotly/dash-table/issues/319)
19+
- New 'loading_state' prop that contains information about which prop, if any, is being computed.
20+
- Table no longer allows for editing while the `data` prop is loading.
21+
1722
## [4.2.0] - 2019-08-27
1823
### Added
1924
[#317](https://github.com/plotly/dash-table/issues/317)

Diff for: packages/dash-table/package.json

+4
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,16 @@
2525
"private::host_dash8081": "python tests/cypress/dash/v_be_page.py",
2626
"private::host_dash8082": "python tests/cypress/dash/v_copy_paste.py",
2727
"private::host_dash8083": "python tests/cypress/dash/v_fe_page.py",
28+
"private::host_dash8084": "python tests/cypress/dash/v_data_loading.py",
29+
"private::host_dash8085": "python tests/cypress/dash/v_default.py",
2830
"private::host_js": "http-server ./dash_table -c-1 --silent",
2931
"private::lint:ts": "tslint --project tsconfig.json --config tslint.json",
3032
"private::lint:py": "flake8 --exclude=DataTable.py,__init__.py,_imports_.py dash_table",
3133
"private::wait_dash8081": "wait-on http://localhost:8081",
3234
"private::wait_dash8082": "wait-on http://localhost:8082",
3335
"private::wait_dash8083": "wait-on http://localhost:8083",
36+
"private::wait_dash8084": "wait-on http://localhost:8084",
37+
"private::wait_dash8085": "wait-on http://localhost:8085",
3438
"private::wait_js": "wait-on http://localhost:8080",
3539
"private::opentests": "cypress open",
3640
"private::test.python": "python -m unittest tests/unit/format_test.py",

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ interface IProps {
1616
dropdown?: IDropdownValue[];
1717
onChange: (e: ChangeEvent) => void;
1818
value: any;
19+
disabled?: boolean;
1920
}
2021

2122
export default class CellDropdown extends PureComponent<IProps> {
@@ -24,7 +25,8 @@ export default class CellDropdown extends PureComponent<IProps> {
2425
clearable,
2526
dropdown,
2627
onChange,
27-
value
28+
value,
29+
disabled
2830
} = this.props;
2931

3032
return (<div className='dash-dropdown-cell-value-container dash-cell-value-container'>
@@ -41,6 +43,7 @@ export default class CellDropdown extends PureComponent<IProps> {
4143
options={dropdown}
4244
placeholder={''}
4345
value={value}
46+
disabled={disabled}
4447
/>
4548
</div>);
4649
}

Diff for: packages/dash-table/src/dash-table/components/CellFactory.tsx

+10-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import { IEdgesMatrices } from 'dash-table/derived/edges/type';
1515
import { memoizeOne } from 'core/memoizer';
1616
import memoizerCache from 'core/cache/memoizer';
1717

18+
import dataLoading from 'dash-table/derived/table/data_loading';
19+
1820
export default class CellFactory {
1921

2022
private get props() {
@@ -51,7 +53,8 @@ export default class CellFactory {
5153
style_data,
5254
style_data_conditional,
5355
virtualized,
54-
visibleColumns
56+
visibleColumns,
57+
loading_state
5558
} = this.props;
5659

5760
const relevantStyles = this.relevantStyles(
@@ -113,12 +116,15 @@ export default class CellFactory {
113116
selected_cells
114117
);
115118

119+
const data_loading = dataLoading(loading_state);
120+
116121
const partialCellContents = this.cellContents.partialGet(
117122
visibleColumns,
118123
virtualized.data,
119124
virtualized.offset,
120125
!!is_focused,
121-
dropdowns
126+
dropdowns,
127+
data_loading
122128
);
123129

124130
const cellContents = this.cellContents.get(
@@ -128,7 +134,8 @@ export default class CellFactory {
128134
virtualized.data,
129135
virtualized.offset,
130136
!!is_focused,
131-
dropdowns
137+
dropdowns,
138+
data_loading
132139
);
133140

134141
const ops = this.getDataOpCells(

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

+10-3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import TableTooltip from './fragments/TableTooltip';
3535

3636
import queryLexicon from 'dash-table/syntax-tree/lexicon/query';
3737

38+
import dataLoading from 'dash-table/derived/table/data_loading';
39+
3840
const DEFAULT_STYLE = {
3941
width: '100%'
4042
};
@@ -581,10 +583,11 @@ export default class ControlledTable extends PureComponent<ControlledTableProps>
581583
sort_by,
582584
viewport,
583585
visibleColumns,
584-
include_headers_on_copy_paste
586+
include_headers_on_copy_paste,
587+
loading_state
585588
} = this.props;
586589

587-
if (!editable || !active_cell) {
590+
if (!editable || !active_cell || dataLoading(loading_state)) {
588591
return;
589592
}
590593

@@ -718,6 +721,7 @@ export default class ControlledTable extends PureComponent<ControlledTableProps>
718721
filter_action,
719722
fixed_columns,
720723
fixed_rows,
724+
loading_state,
721725
scrollbarWidth,
722726
style_as_list_view,
723727
style_table,
@@ -733,6 +737,8 @@ export default class ControlledTable extends PureComponent<ControlledTableProps>
733737
visibleColumns
734738
} = this.props;
735739

740+
const isLoading = dataLoading(loading_state);
741+
736742
const fragmentClasses = [
737743
[
738744
fixed_rows && fixed_columns ? 'dash-fixed-row dash-fixed-column' : '',
@@ -763,7 +769,8 @@ export default class ControlledTable extends PureComponent<ControlledTableProps>
763769
...(visibleColumns.length ? [] : ['dash-no-columns']),
764770
...(virtualized.data.length ? [] : ['dash-no-data']),
765771
...(filter_action !== TableAction.None ? [] : ['dash-no-filter']),
766-
...(fill_width ? ['dash-fill-width'] : [])
772+
...(fill_width ? ['dash-fill-width'] : []),
773+
...(isLoading ? ['dash-loading'] : [])
767774
];
768775

769776
const containerClasses = ['dash-spreadsheet-container', ...classes];

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,4 @@ export default () => {
112112
}
113113
]) as ControlledTableProps;
114114
};
115-
};
115+
};

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

+12
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,12 @@ export interface IBaseColumn {
164164
name: string | string[];
165165
}
166166

167+
export interface ILoadingState {
168+
is_loading: boolean;
169+
prop_name: string | undefined;
170+
component_name: string | undefined;
171+
}
172+
167173
export type ConditionalDropdowns = IConditionalDropdown[];
168174
export type DataDropdowns = Partial<IDataDropdowns>[];
169175
export type DataTooltips = Partial<ITableTooltips>[];
@@ -307,6 +313,8 @@ export interface IProps {
307313
style_header_conditional?: Headers;
308314
style_table?: Table;
309315
virtualization?: boolean;
316+
317+
loading_state?: ILoadingState;
310318
}
311319

312320
interface IDefaultProps {
@@ -401,6 +409,8 @@ export type ControlledTableProps = SanitizedProps & IState & {
401409
virtual: IDerivedData;
402410
virtual_selected_rows: Indices;
403411
virtualized: IVirtualizedDerivedData;
412+
413+
loading_state: ILoadingState | undefined;
404414
};
405415

406416
export type SetFilter = (
@@ -468,4 +478,6 @@ export interface ICellFactoryProps {
468478
virtualization: boolean;
469479
virtualized: IVirtualizedDerivedData;
470480
visibleColumns: Columns;
481+
482+
loading_state?: ILoadingState;
471483
}

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

+20-1
Original file line numberDiff line numberDiff line change
@@ -1206,7 +1206,26 @@ export const propTypes = {
12061206
*/
12071207
derived_virtual_selected_row_ids: PropTypes.arrayOf(
12081208
PropTypes.oneOfType([PropTypes.string, PropTypes.number])
1209-
)
1209+
),
1210+
1211+
/**
1212+
* Object that holds the loading state object coming from dash-renderer
1213+
*/
1214+
loading_state: PropTypes.shape({
1215+
/**
1216+
* Determines if the component is loading or not
1217+
*/
1218+
is_loading: PropTypes.bool,
1219+
/**
1220+
* Holds which property is loading
1221+
*/
1222+
prop_name: PropTypes.string,
1223+
/**
1224+
* Holds the name of the component that is loading
1225+
*/
1226+
component_name: PropTypes.string
1227+
})
1228+
12101229
};
12111230

12121231
DataTable.defaultProps = defaultProps;

Diff for: packages/dash-table/src/dash-table/derived/cell/contents.tsx

+16-9
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,16 @@ function getCellType(
3535
active: boolean,
3636
editable: boolean,
3737
dropdown: IDropdownValue[] | undefined,
38-
presentation: Presentation | undefined
38+
presentation: Presentation | undefined,
39+
is_loading: boolean
3940
): CellType {
4041
switch (presentation) {
4142
case Presentation.Input:
42-
return (!active || !editable) ? CellType.Label : CellType.Input;
43+
return (!active || !editable || is_loading) ? CellType.Label : CellType.Input;
4344
case Presentation.Dropdown:
4445
return (!dropdown || !editable) ? CellType.DropdownLabel : CellType.Dropdown;
4546
default:
46-
return (!active || !editable) ? CellType.Label : CellType.Input;
47+
return (!active || !editable || is_loading) ? CellType.Label : CellType.Input;
4748
}
4849
}
4950

@@ -62,7 +63,8 @@ class Contents {
6263
data: Data,
6364
_offset: IViewportOffset,
6465
isFocused: boolean,
65-
dropdowns: (IDropdown | undefined)[][]
66+
dropdowns: (IDropdown | undefined)[][],
67+
data_loading: boolean
6668
): JSX.Element[][] => {
6769
const formatters = R.map(getFormatter, columns);
6870

@@ -76,7 +78,8 @@ class Contents {
7678
columnIndex,
7779
rowIndex,
7880
datum,
79-
formatters
81+
formatters,
82+
data_loading
8083
), columns), data);
8184
});
8285

@@ -87,7 +90,8 @@ class Contents {
8790
data: Data,
8891
offset: IViewportOffset,
8992
isFocused: boolean,
90-
dropdowns: (IDropdown | undefined)[][]
93+
dropdowns: (IDropdown | undefined)[][],
94+
data_loading: boolean
9195
): JSX.Element[][] => {
9296
if (!activeCell) {
9397
return contents;
@@ -112,20 +116,22 @@ class Contents {
112116
jActive,
113117
iActive,
114118
data[i],
115-
formatters
119+
formatters,
120+
data_loading
116121
);
117122

118123
return contents;
119124
});
120125

121-
private getContent(active: boolean, isFocused: boolean, column: IColumn, dropdown: IDropdown | undefined, columnIndex: number, rowIndex: number, datum: any, formatters: ((value: any) => any)[]) {
126+
private getContent(active: boolean, isFocused: boolean, column: IColumn, dropdown: IDropdown | undefined, columnIndex: number, rowIndex: number, datum: any, formatters: ((value: any) => any)[], data_loading: boolean) {
127+
122128
const className = [
123129
...(active ? ['input-active'] : []),
124130
isFocused ? 'focused' : 'unfocused',
125131
'dash-cell-value'
126132
].join(' ');
127133

128-
const cellType = getCellType(active, column.editable, dropdown && dropdown.options, column.presentation);
134+
const cellType = getCellType(active, column.editable, dropdown && dropdown.options, column.presentation, data_loading);
129135

130136
switch (cellType) {
131137
case CellType.Dropdown:
@@ -136,6 +142,7 @@ class Contents {
136142
dropdown={dropdown && dropdown.options}
137143
onChange={this.handlers(Handler.Change, rowIndex, columnIndex)}
138144
value={datum[column.id]}
145+
disabled={data_loading}
139146
/>);
140147
case CellType.Input:
141148
return (<CellInput
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { ILoadingState } from 'dash-table/components/Table/props';
2+
3+
export default function dataLoading(
4+
loading_state: ILoadingState | undefined
5+
) {
6+
return (loading_state && loading_state.is_loading && (loading_state.prop_name === 'data' || loading_state.prop_name === '' || loading_state.prop_name === undefined) ? true : false);
7+
}

Diff for: packages/dash-table/tests/cypress/dash/v_copy_paste.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# pylint: disable=global-statement
22
import dash
33
from dash.dependencies import Input, Output, State
4+
from dash.exceptions import PreventUpdate
45
import dash_html_components as html
56
import os
67
import pandas as pd
@@ -85,15 +86,20 @@
8586
# pylint: disable=unused-argument
8687
def updateData(timestamp, current, previous):
8788
# pylint: enable=unused-argument
88-
if current is None or previous is None:
89-
return current
89+
if timestamp is None or current is None or previous is None:
90+
raise PreventUpdate
9091

92+
modified = False
9193
if len(current) == len(previous):
9294
for (i, datum) in enumerate(current):
9395
previous_datum = previous[i]
9496
if datum[0] != previous_datum[0]:
97+
modified = True
9598
datum[1] = "MODIFIED"
9699

100+
if not modified:
101+
raise PreventUpdate
102+
97103
return current
98104

99105
if __name__ == "__main__":

0 commit comments

Comments
 (0)