Skip to content

Commit b6a1197

Browse files
authored
Merge pull request #1376 from plotly/persistence-hg
PersistenceTransforms for date in datePickerRange and datePickerSingle
2 parents fa43741 + b5f78b3 commit b6a1197

File tree

6 files changed

+391
-5
lines changed

6 files changed

+391
-5
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import React, {PureComponent} from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
const isEquivalent = (v1, v2) => v1 === v2 || (isNaN(v1) && isNaN(v2));
5+
6+
const omit = (key, obj) => {
7+
const { [key]: omitted, ...rest } = obj;
8+
return rest;
9+
}
10+
11+
/**
12+
* Adapted dcc input component for persistence tests.
13+
*
14+
* Note that unnecessary props have been removed.
15+
*/
16+
export default class MyPersistedComponent extends PureComponent {
17+
constructor(props) {
18+
super(props);
19+
this.input = React.createRef();
20+
this.onChange = this.onChange.bind(this);
21+
this.onEvent = this.onEvent.bind(this);
22+
this.onKeyPress = this.onKeyPress.bind(this);
23+
this.setInputValue = this.setInputValue.bind(this);
24+
this.setPropValue = this.setPropValue.bind(this);
25+
}
26+
27+
UNSAFE_componentWillReceiveProps(nextProps) {
28+
const {value} = this.input.current;
29+
this.setInputValue(
30+
value,
31+
nextProps.value
32+
);
33+
this.setState({value: nextProps.value});
34+
}
35+
36+
componentDidMount() {
37+
const {value} = this.input.current;
38+
this.setInputValue(
39+
value,
40+
this.props.value
41+
);
42+
}
43+
44+
UNSAFE_componentWillMount() {
45+
this.setState({value: this.props.value})
46+
}
47+
48+
render() {
49+
const valprops = {value: this.state.value}
50+
return (
51+
<input
52+
ref={this.input}
53+
onChange={this.onChange}
54+
onKeyPress={this.onKeyPress}
55+
{...valprops}
56+
{...omit(
57+
[
58+
'value',
59+
'setProps',
60+
],
61+
this.props
62+
)}
63+
/>
64+
);
65+
}
66+
67+
setInputValue(base, value) {
68+
base = NaN;
69+
70+
if (!isEquivalent(base, value)) {
71+
this.input.current.value = value
72+
}
73+
}
74+
75+
setPropValue(base, value) {
76+
if (!isEquivalent(base, value)) {
77+
this.props.setProps({value});
78+
}
79+
}
80+
81+
onEvent() {
82+
const {value} = this.input.current;
83+
this.props.setProps({value})
84+
}
85+
86+
onKeyPress(e) {
87+
return this.onEvent();
88+
}
89+
90+
onChange() {
91+
this.onEvent()
92+
}
93+
}
94+
95+
MyPersistedComponent.defaultProps = {
96+
persisted_props: ['value'],
97+
persistence_type: 'local',
98+
};
99+
100+
MyPersistedComponent.propTypes = {
101+
/**
102+
* The ID of this component, used to identify dash components
103+
* in callbacks. The ID needs to be unique across all of the
104+
* components in an app.
105+
*/
106+
id: PropTypes.string,
107+
108+
/**
109+
* The value of the input
110+
*/
111+
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
112+
113+
/**
114+
* The name of the control, which is submitted with the form data.
115+
*/
116+
name: PropTypes.string,
117+
118+
/**
119+
* Dash-assigned callback that gets fired when the value changes.
120+
*/
121+
setProps: PropTypes.func,
122+
123+
124+
/**
125+
* Used to allow user interactions in this component to be persisted when
126+
* the component - or the page - is refreshed. If `persisted` is truthy and
127+
* hasn't changed from its previous value, a `value` that the user has
128+
* changed while using the app will keep that change, as long as
129+
* the new `value` also matches what was given originally.
130+
* Used in conjunction with `persistence_type`.
131+
*/
132+
persistence: PropTypes.oneOfType([
133+
PropTypes.bool,
134+
PropTypes.string,
135+
PropTypes.number,
136+
]),
137+
138+
/**
139+
* Properties whose user interactions will persist after refreshing the
140+
* component or the page. Since only `value` is allowed this prop can
141+
* normally be ignored.
142+
*/
143+
persisted_props: PropTypes.arrayOf(PropTypes.oneOf(['value'])),
144+
145+
/**
146+
* Where persisted user changes will be stored:
147+
* memory: only kept in memory, reset on page refresh.
148+
* local: window.localStorage, data is kept after the browser quit.
149+
* session: window.sessionStorage, data is cleared once the browser quit.
150+
*/
151+
persistence_type: PropTypes.oneOf(['local', 'session', 'memory']),
152+
};
153+
154+
MyPersistedComponent.persistenceTransforms = {
155+
value: {
156+
157+
extract: propValue => {
158+
if (!(propValue === null || propValue === undefined)) {
159+
return propValue.toUpperCase();
160+
}
161+
return propValue;
162+
},
163+
apply: storedValue => storedValue,
164+
165+
},
166+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import React, {PureComponent} from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
const isEquivalent = (v1, v2) => v1 === v2 || (isNaN(v1) && isNaN(v2));
5+
6+
const omit = (key, obj) => {
7+
const { [key]: omitted, ...rest } = obj;
8+
return rest;
9+
}
10+
11+
/**
12+
* Adapted dcc input component for persistence tests.
13+
*
14+
* Note that unnecessary props have been removed.
15+
*/
16+
export default class MyPersistedComponentNested extends PureComponent {
17+
constructor(props) {
18+
super(props);
19+
this.input = React.createRef();
20+
this.onChange = this.onChange.bind(this);
21+
this.onEvent = this.onEvent.bind(this);
22+
this.onKeyPress = this.onKeyPress.bind(this);
23+
this.setInputValue = this.setInputValue.bind(this);
24+
this.setPropValue = this.setPropValue.bind(this);
25+
}
26+
27+
UNSAFE_componentWillReceiveProps(nextProps) {
28+
const {value} = this.input.current;
29+
this.setInputValue(
30+
value,
31+
nextProps.value
32+
);
33+
this.setState({value: nextProps.value});
34+
}
35+
36+
componentDidMount() {
37+
const {value} = this.input.current;
38+
this.setInputValue(
39+
value,
40+
this.props.value
41+
);
42+
}
43+
44+
UNSAFE_componentWillMount() {
45+
this.setState({value: this.props.value})
46+
}
47+
48+
render() {
49+
const valprops = {value: this.state.value}
50+
return (
51+
<input
52+
ref={this.input}
53+
onChange={this.onChange}
54+
onKeyPress={this.onKeyPress}
55+
{...valprops}
56+
{...omit(
57+
[
58+
'value',
59+
'setProps',
60+
],
61+
this.props
62+
)}
63+
/>
64+
);
65+
}
66+
67+
setInputValue(base, value) {
68+
base = NaN;
69+
70+
if (!isEquivalent(base, value)) {
71+
this.input.current.value = value
72+
}
73+
}
74+
75+
setPropValue(base, value) {
76+
if (!isEquivalent(base, value)) {
77+
this.props.setProps({value});
78+
}
79+
}
80+
81+
onEvent() {
82+
const {value} = this.input.current;
83+
this.props.setProps({value})
84+
}
85+
86+
onKeyPress(e) {
87+
return this.onEvent();
88+
}
89+
90+
onChange() {
91+
this.onEvent()
92+
}
93+
}
94+
95+
MyPersistedComponentNested.defaultProps = {
96+
persisted_props: ['value.nested_value'],
97+
persistence_type: 'local',
98+
};
99+
100+
MyPersistedComponentNested.propTypes = {
101+
/**
102+
* The ID of this component, used to identify dash components
103+
* in callbacks. The ID needs to be unique across all of the
104+
* components in an app.
105+
*/
106+
id: PropTypes.string,
107+
108+
/**
109+
* The value of the input
110+
*/
111+
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
112+
113+
/**
114+
* The name of the control, which is submitted with the form data.
115+
*/
116+
name: PropTypes.string,
117+
118+
/**
119+
* Dash-assigned callback that gets fired when the value changes.
120+
*/
121+
setProps: PropTypes.func,
122+
123+
124+
/**
125+
* Used to allow user interactions in this component to be persisted when
126+
* the component - or the page - is refreshed. If `persisted` is truthy and
127+
* hasn't changed from its previous value, a `value` that the user has
128+
* changed while using the app will keep that change, as long as
129+
* the new `value` also matches what was given originally.
130+
* Used in conjunction with `persistence_type`.
131+
*/
132+
persistence: PropTypes.oneOfType([
133+
PropTypes.bool,
134+
PropTypes.string,
135+
PropTypes.number,
136+
]),
137+
138+
/**
139+
* Properties whose user interactions will persist after refreshing the
140+
* component or the page. Since only `value` is allowed this prop can
141+
* normally be ignored.
142+
*/
143+
persisted_props: PropTypes.arrayOf(PropTypes.oneOf(['value.nested_value'])),
144+
145+
/**
146+
* Where persisted user changes will be stored:
147+
* memory: only kept in memory, reset on page refresh.
148+
* local: window.localStorage, data is kept after the browser quit.
149+
* session: window.sessionStorage, data is cleared once the browser quit.
150+
*/
151+
persistence_type: PropTypes.oneOf(['local', 'session', 'memory']),
152+
};
153+
154+
MyPersistedComponentNested.persistenceTransforms = {
155+
value: {
156+
157+
nested_value: {
158+
159+
extract: propValue => {
160+
if (!(propValue === null || propValue === undefined)) {
161+
return propValue.toUpperCase();
162+
}
163+
return propValue;
164+
},
165+
apply: storedValue => storedValue,
166+
167+
}
168+
},
169+
};

Diff for: @plotly/dash-test-components/src/index.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import StyledComponent from './components/StyledComponent';
2+
import MyPersistedComponent from './components/MyPersistedComponent';
3+
import MyPersistedComponentNested from './components/MyPersistedComponentNested';
4+
25

36
export {
4-
StyledComponent,
7+
StyledComponent, MyPersistedComponent, MyPersistedComponentNested
58
};

Diff for: CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
All notable changes to `dash` will be documented in this file.
33
This project adheres to [Semantic Versioning](https://semver.org/).
44

5+
## [UNRELEASED]
6+
### Changed
7+
- [#1376](https://github.com/plotly/dash/pull/1376) Extends the `getTransform` logic in the renderer to handle `persistenceTransforms` for both nested and non-nested persisted props. This was used to to fix [dcc#700](https://github.com/plotly/dash-core-components/issues/700) in conjunction with [dcc#854](https://github.com/plotly/dash-core-components/pull/854) by using persistenceTransforms to strip the time part of the datetime so that datepickers can persist when defined in callbacks.
8+
59
## [1.16.0] - 2020-09-03
610
### Added
711
- [#1371](https://github.com/plotly/dash/pull/1371) You can now get [CSP `script-src` hashes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src) of all added inline scripts by calling `app.csp_hashes()` (both Dash internal inline scripts, and those added with `app.clientside_callback`) .

Diff for: dash-renderer/src/persistence.js

+12-4
Original file line numberDiff line numberDiff line change
@@ -265,10 +265,18 @@ const noopTransform = {
265265
apply: (storedValue, _propValue) => storedValue
266266
};
267267

268-
const getTransform = (element, propName, propPart) =>
269-
propPart
270-
? element.persistenceTransforms[propName][propPart]
271-
: noopTransform;
268+
const getTransform = (element, propName, propPart) => {
269+
if (
270+
element.persistenceTransforms &&
271+
element.persistenceTransforms[propName]
272+
) {
273+
if (propPart) {
274+
return element.persistenceTransforms[propName][propPart];
275+
}
276+
return element.persistenceTransforms[propName];
277+
}
278+
return noopTransform;
279+
};
272280

273281
const getValsKey = (id, persistedProp, persistence) =>
274282
`${stringifyId(id)}.${persistedProp}.${JSON.stringify(persistence)}`;

0 commit comments

Comments
 (0)