From 3d39d443885dd98e096c5efddb540eb2a10411cc Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Mon, 23 Jul 2018 14:28:39 -0400 Subject: [PATCH 1/4] Fix display of ConfirmDialogProvider if not used in a callback. --- src/components/ConfirmDialog.react.js | 48 ++++++++++++++----- src/components/ConfirmDialogProvider.react.js | 21 ++++++-- 2 files changed, 52 insertions(+), 17 deletions(-) diff --git a/src/components/ConfirmDialog.react.js b/src/components/ConfirmDialog.react.js index b7b7683ab..800d7ae6d 100644 --- a/src/components/ConfirmDialog.react.js +++ b/src/components/ConfirmDialog.react.js @@ -1,6 +1,7 @@ import PropTypes from 'prop-types'; import {Component} from 'react'; + /** * ConfirmDialog is used to display the browser's native "confirm" modal, * with an optional message and two buttons ("OK" and "Cancel"). @@ -11,30 +12,51 @@ export default class ConfirmDialog extends Component { constructor(props) { super(props); + this.state = { + displayed: props.displayed + }; } - componentDidUpdate() { + _setStateAndProps(value) { + const { setProps } = this.props; + this.setState(value); + if (setProps) setProps(value); + } + + componentWillReceiveProps(props) { + this.setState({displayed: props.displayed}) + } + + _update() { const { - displayed, message, setProps, + message, cancel_n_clicks, cancel_n_clicks_timestamp, submit_n_clicks, submit_n_clicks_timestamp } = this.props; + const displayed = this.state.displayed; + if (displayed) { - new Promise(resolve => resolve(window.confirm(message))).then(result => setProps({ - cancel_n_clicks: !result ? - cancel_n_clicks + 1 : cancel_n_clicks, - cancel_n_clicks_timestamp: !result ? - Date.now() : cancel_n_clicks_timestamp, - submit_n_clicks: result ? - submit_n_clicks + 1: submit_n_clicks, - submit_n_clicks_timestamp: result ? - Date.now() : submit_n_clicks_timestamp, - displayed: false - })); + new Promise(resolve => resolve(window.confirm(message))).then(result => { + this._setStateAndProps({ + cancel_n_clicks: !result ? + cancel_n_clicks + 1 : cancel_n_clicks, + cancel_n_clicks_timestamp: !result ? + Date.now() : cancel_n_clicks_timestamp, + submit_n_clicks: result ? + submit_n_clicks + 1: submit_n_clicks, + submit_n_clicks_timestamp: result ? + Date.now() : submit_n_clicks_timestamp, + displayed: false + }); + }); } } + componentDidUpdate() { + this._update() + } + render() { return null; } diff --git a/src/components/ConfirmDialogProvider.react.js b/src/components/ConfirmDialogProvider.react.js index b09e3bac5..29fa0a867 100644 --- a/src/components/ConfirmDialogProvider.react.js +++ b/src/components/ConfirmDialogProvider.react.js @@ -18,15 +18,26 @@ import ConfirmDialog from './ConfirmDialog.react' * ``` */ export default class ConfirmDialogProvider extends React.Component { + constructor(props) { + super(props); + this.state = {displayed: props.displayed}; + } + + componentWillReceiveProps(props) { + this.setState({displayed: props.displayed}) + } + render() { const { id, setProps, children } = this.props; + const displayed = this.state.displayed; + // Will lose the previous onClick of the child const wrapClick = (child) => React.cloneElement(child, {onClick: () => { - setProps({ - displayed: true - }); + const update = {displayed: true}; + this.setState(update); + if (setProps) setProps(update); } }); @@ -41,7 +52,9 @@ export default class ConfirmDialogProvider extends React.Component { ? realChild.map(wrapClick) : wrapClick(realChild) } - + ) } From db89054e3d143973cac4685e3e65f553ab161954 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Mon, 13 Aug 2018 11:07:14 -0400 Subject: [PATCH 2/4] Fix confirm callbacks sent to both submit and cancel. --- src/components/ConfirmDialog.react.js | 33 +++++++++++++++------------ 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/components/ConfirmDialog.react.js b/src/components/ConfirmDialog.react.js index 800d7ae6d..67a6b2302 100644 --- a/src/components/ConfirmDialog.react.js +++ b/src/components/ConfirmDialog.react.js @@ -15,11 +15,12 @@ export default class ConfirmDialog extends Component { this.state = { displayed: props.displayed }; + this._update(); } _setStateAndProps(value) { const { setProps } = this.props; - this.setState(value); + this.setState({displayed: value.displayed}); if (setProps) setProps(value); } @@ -30,25 +31,28 @@ export default class ConfirmDialog extends Component { _update() { const { message, - cancel_n_clicks, cancel_n_clicks_timestamp, - submit_n_clicks, submit_n_clicks_timestamp + cancel_n_clicks, + submit_n_clicks } = this.props; const displayed = this.state.displayed; if (displayed) { new Promise(resolve => resolve(window.confirm(message))).then(result => { - this._setStateAndProps({ - cancel_n_clicks: !result ? - cancel_n_clicks + 1 : cancel_n_clicks, - cancel_n_clicks_timestamp: !result ? - Date.now() : cancel_n_clicks_timestamp, - submit_n_clicks: result ? - submit_n_clicks + 1: submit_n_clicks, - submit_n_clicks_timestamp: result ? - Date.now() : submit_n_clicks_timestamp, - displayed: false - }); + + if (result) { + this._setStateAndProps({ + submit_n_clicks: submit_n_clicks + 1, + submit_n_clicks_timestamp: Date.now(), + displayed: false + }); + } else { + this._setStateAndProps({ + cancel_n_clicks: cancel_n_clicks + 1, + cancel_n_clicks_timestamp: Date.now(), + displayed: false + }); + } }); } } @@ -96,6 +100,7 @@ ConfirmDialog.propTypes = { * Set to true to send the ConfirmDialog. */ displayed: PropTypes.bool, + key: PropTypes.string, /** * Dash-assigned callback that gets fired when the value changes. From a2c4a3b533e0cf8dc23641d1de1c960ba47cc824 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Mon, 13 Aug 2018 11:08:08 -0400 Subject: [PATCH 3/4] Add tests for ConfirmDialog without callbacks. --- test/test_integration.py | 86 ++++++++++++++++++++++++++++++---------- 1 file changed, 66 insertions(+), 20 deletions(-) diff --git a/test/test_integration.py b/test/test_integration.py index b318bd7e9..de5468801 100644 --- a/test/test_integration.py +++ b/test/test_integration.py @@ -728,23 +728,24 @@ def update_text(n): output = self.wait_for_element_by_css_selector('#output') self.assertEqual(output.text, '2') - def _test_confirm(self, app, test_name): + def _test_confirm(self, app, test_name, add_callback=True): count = Value('i', 0) - @app.callback(Output('confirmed', 'children'), - [Input('confirm', 'submit_n_clicks'), - Input('confirm', 'cancel_n_clicks')], - [State('confirm', 'submit_n_clicks_timestamp'), - State('confirm', 'cancel_n_clicks_timestamp')]) - def _on_confirmed(submit_n_clicks, cancel_n_clicks, - submit_timestamp, cancel_timestamp): - if not submit_n_clicks and not cancel_n_clicks: - return '' - count.value = submit_n_clicks + cancel_n_clicks - if submit_timestamp > cancel_timestamp: - return 'confirmed' - else: - return 'canceled' + if add_callback: + @app.callback(Output('confirmed', 'children'), + [Input('confirm', 'submit_n_clicks'), + Input('confirm', 'cancel_n_clicks')], + [State('confirm', 'submit_n_clicks_timestamp'), + State('confirm', 'cancel_n_clicks_timestamp')]) + def _on_confirmed(submit_n_clicks, cancel_n_clicks, + submit_timestamp, cancel_timestamp): + if not submit_n_clicks and not cancel_n_clicks: + return '' + count.value += 1 + if submit_timestamp > cancel_timestamp: + return 'confirmed' + else: + return 'canceled' self.startServer(app) self.snapshot(test_name + ' -> initial') @@ -753,17 +754,23 @@ def _on_confirmed(submit_n_clicks, cancel_n_clicks, button.click() time.sleep(1) self.driver.switch_to.alert.accept() - self.wait_for_text_to_equal('#confirmed', 'confirmed') - self.snapshot(test_name + ' -> confirmed') + + if add_callback: + self.wait_for_text_to_equal('#confirmed', 'confirmed') + self.snapshot(test_name + ' -> confirmed') button.click() time.sleep(0.5) self.driver.switch_to.alert.dismiss() time.sleep(0.5) - self.wait_for_text_to_equal('#confirmed', 'canceled') - self.snapshot(test_name + ' -> canceled') - self.assertEqual(2, count.value, 'Expected 2 callback but got ' + str(count.value)) + if add_callback: + self.wait_for_text_to_equal('#confirmed', 'canceled') + self.snapshot(test_name + ' -> canceled') + + if add_callback: + self.assertEqual(2, count.value, + 'Expected 2 callback but got ' + str(count.value)) def test_confirm(self): app = dash.Dash(__name__) @@ -793,3 +800,42 @@ def test_confirm_dialog_provider(self): ]) self._test_confirm(app, 'ConfirmDialogProvider') + + def test_confirm_without_callback(self): + app = dash.Dash(__name__) + app.layout = html.Div([ + dcc.ConfirmDialogProvider( + html.Button('click me', id='button'), + id='confirm', message='Please confirm.'), + html.Div(id='confirmed') + ]) + self._test_confirm(app, 'ConfirmDialogProviderWithoutCallback', + add_callback=False) + + def test_confirm_as_children(self): + app = dash.Dash(__name__) + + app.layout = html.Div([ + html.Button(id='button', children='Send confirm'), + html.Div(id='confirm-container'), + dcc.Location(id='dummy-location') + ]) + + @app.callback(Output('confirm-container', 'children'), + [Input('button', 'n_clicks')]) + def on_click(n_clicks): + if n_clicks: + return dcc.ConfirmDialog( + displayed=True, + id='confirm', + key='confirm-{}'.format(time.time()), + message='Please confirm.') + + self.startServer(app) + + button = self.wait_for_element_by_css_selector('#button') + + button.click() + time.sleep(2) + + self.driver.switch_to.alert.accept() From 1c9fd82c4218e34cb66c49acf7231e2ed416a138 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Mon, 13 Aug 2018 15:13:59 -0400 Subject: [PATCH 4/4] Update version, changelogs, bundle. --- CHANGELOG.md | 5 + dash_core_components/Checklist.py | 138 +++++----- dash_core_components/ConfirmDialog.py | 131 ++++----- dash_core_components/ConfirmDialogProvider.py | 144 +++++----- dash_core_components/DatePickerRange.py | 250 ++++++++--------- dash_core_components/DatePickerSingle.py | 224 +++++++-------- dash_core_components/Dropdown.py | 156 +++++------ dash_core_components/Graph.py | 256 +++++++++--------- dash_core_components/Input.py | 184 ++++++------- dash_core_components/Interval.py | 130 ++++----- dash_core_components/Location.py | 124 ++++----- dash_core_components/Markdown.py | 122 ++++----- dash_core_components/RadioItems.py | 138 +++++----- dash_core_components/RangeSlider.py | 188 ++++++------- dash_core_components/Slider.py | 172 ++++++------ dash_core_components/Tab.py | 136 +++++----- dash_core_components/Tabs.py | 160 +++++------ dash_core_components/Textarea.py | 166 ++++++------ dash_core_components/Upload.py | 168 ++++++------ dash_core_components/bundle.js | 78 +++--- dash_core_components/metadata.json | 231 +++++++++------- dash_core_components/version.py | 2 +- package-lock.json | 149 +++++----- package.json | 4 +- 24 files changed, 1747 insertions(+), 1709 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 858e5590a..368dc09f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [0.27.1] +### Fixed +- `ConfirmDialogProvider` can now be used without a callback. [#241](https://github.com/plotly/dash-core-components/pull/241) +- `ConfirmDialog`, only fire `submit` when `submit` is clicked. [#242](https://github.com/plotly/dash-core-components/issues/242) fixed in [#241](https://github.com/plotly/dash-core-components/pull/241) + ## [0.27.0] ### Changed - `dash_core_components/__init__.py` now imports from python class files rather than generating classes at runtime, diff --git a/dash_core_components/Checklist.py b/dash_core_components/Checklist.py index 086911e9c..00641210c 100644 --- a/dash_core_components/Checklist.py +++ b/dash_core_components/Checklist.py @@ -1,69 +1,69 @@ -# AUTO GENERATED FILE - DO NOT EDIT - -from dash.development.base_component import Component, _explicitize_args - - -class Checklist(Component): - """A Checklist component. -Checklist is a component that encapsulates several checkboxes. -The values and labels of the checklist is specified in the `options` -property and the checked items are specified with the `values` property. -Each checkbox is rendered as an input with a surrounding label. - -Keyword arguments: -- id (string; optional) -- options (list; optional): An array of options -- values (list; optional): The currently selected value -- className (string; optional): The class of the container (div) -- style (dict; optional): The style of the container (div) -- inputStyle (dict; optional): The style of the checkbox element -- inputClassName (string; optional): The class of the checkbox element -- labelStyle (dict; optional): The style of the