-
-
Notifications
You must be signed in to change notification settings - Fork 144
Confirmation modal #211
Confirmation modal #211
Changes from 11 commits
425c811
75eafb5
60ae0c2
eb8adce
ab3ae1f
49253d6
a95d578
02e5d34
91926b5
86d86e2
497bc73
f8a3b38
d4adc7a
aac2115
ac90a45
93ed6a7
727521a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import PropTypes from 'prop-types'; | ||
import {Component} from 'react'; | ||
|
||
/** | ||
* ConfirmDialog wraps window.confirm | ||
*/ | ||
export default class ConfirmDialog extends Component { | ||
|
||
constructor(props) { | ||
super(props); | ||
} | ||
|
||
componentDidUpdate() { | ||
const { displayed, message, send_confirm, setProps, cancel_n_clicks, submit_n_clicks, n_clicks } = this.props; | ||
|
||
if (send_confirm && !displayed) { | ||
setProps({send_confirm: false, displayed: true}); | ||
new Promise(resolve => resolve(window.confirm(message))).then(result => setProps({ | ||
n_clicks: n_clicks + 1, | ||
n_clicks_timestamp: Date.now(), | ||
cancel_n_clicks: !result ? cancel_n_clicks + 1 : cancel_n_clicks, | ||
submit_n_clicks: result ? submit_n_clicks + 1: submit_n_clicks, | ||
displayed: false, | ||
})); | ||
} | ||
} | ||
|
||
render() { | ||
return null; | ||
} | ||
} | ||
|
||
ConfirmDialog.defaultProps = { | ||
n_clicks: 0, | ||
n_clicks_timestamp: -1, | ||
submit_n_clicks: 0, | ||
cancel_n_clicks: 0, | ||
}; | ||
|
||
ConfirmDialog.propTypes = { | ||
id: PropTypes.string, | ||
|
||
/** | ||
* Message to show in the popup. | ||
*/ | ||
message: PropTypes.string, | ||
|
||
/** | ||
* Number of times the modal was submited or canceled. | ||
*/ | ||
n_clicks: PropTypes.number, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given that we have |
||
/** | ||
* Last timestamp the popup was clicked. | ||
*/ | ||
n_clicks_timestamp: PropTypes.number, | ||
/** | ||
* Number of times the submit was clicked | ||
*/ | ||
submit_n_clicks: PropTypes.number, | ||
/** | ||
* Number of times the popup was canceled. | ||
*/ | ||
cancel_n_clicks: PropTypes.number, | ||
/** | ||
* Set to true to send the popup. | ||
*/ | ||
send_confirm: PropTypes.bool, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For some reason, |
||
/** | ||
* Is the modal currently displayed. | ||
*/ | ||
displayed: PropTypes.bool, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that I thought that
Does that work? Or am I missing a functional difference between There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Send_confirm is for activating the modal, it get sets to false after fire, displayed was for telling if the confirmation was currently showing. I just changed for just displayed and it works the same. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think a good rule to keep in mind here and with most functional/React style coding is that prop names should not be verbs, especially not imperative ones like |
||
|
||
/** | ||
* Dash-assigned callback that gets fired when the value changes. | ||
*/ | ||
setProps: PropTypes.func | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
|
||
import ConfirmDialog from './ConfirmDialog.react' | ||
|
||
|
||
|
||
/** | ||
* Wrap children onClick to send a confirmation dialog. | ||
*/ | ||
export default class ConfirmDialogProvider extends React.Component { | ||
render() { | ||
const { id, setProps, children } = this.props; | ||
|
||
// Will lose the previous onClick of the child | ||
const wrapClick = (child) => React.cloneElement(child, {onClick: () => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice, this seems like a good solution. |
||
{ | ||
setProps({ | ||
send_confirm: true | ||
}); | ||
} | ||
}); | ||
|
||
const realChild = children.props | ||
? children.props.children | ||
: children.map(e => e.props.children); | ||
|
||
return ( | ||
<div id={id}> | ||
{ | ||
realChild && realChild.length | ||
? realChild.map(wrapClick) | ||
: wrapClick(realChild) | ||
} | ||
<ConfirmDialog {...this.props}/> | ||
</div> | ||
) | ||
} | ||
}; | ||
|
||
ConfirmDialogProvider.defaultProps = { | ||
n_clicks: 0, | ||
n_clicks_timestamp: -1, | ||
submit_n_clicks: 0, | ||
cancel_n_clicks: 0, | ||
}; | ||
|
||
ConfirmDialogProvider.propTypes = { | ||
id: PropTypes.string, | ||
|
||
/** | ||
* Message to show in the popup. | ||
*/ | ||
message: PropTypes.string, | ||
|
||
/** | ||
* Number of times the modal was submited or canceled. | ||
*/ | ||
n_clicks: PropTypes.number, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm still wondering if there is a use case for this property given that we have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I add the timestamps for submit and cancel, I don't see one as I was only using it to know which one was clicked in the test and it wasn't a real use case. I will remove it. |
||
/** | ||
* Last timestamp the popup was clicked. | ||
*/ | ||
n_clicks_timestamp: PropTypes.number, | ||
/** | ||
* Number of times the submit was clicked | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
*/ | ||
submit_n_clicks: PropTypes.number, | ||
/** | ||
* Number of times the popup was canceled. | ||
*/ | ||
cancel_n_clicks: PropTypes.number, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add |
||
/** | ||
* Set to true to send the popup. | ||
*/ | ||
send_confirm: PropTypes.bool, | ||
/** | ||
* Is the modal currently displayed. | ||
*/ | ||
displayed: PropTypes.bool, | ||
|
||
/** | ||
* Dash-assigned callback that gets fired when the value changes. | ||
*/ | ||
setProps: PropTypes.func, | ||
children: PropTypes.any, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,8 +15,9 @@ | |
from selenium import webdriver | ||
from selenium.webdriver.common.keys import Keys | ||
from selenium.common.exceptions import InvalidElementStateException | ||
import time | ||
|
||
from textwrap import dedent | ||
|
||
try: | ||
from urlparse import urlparse | ||
except ImportError: | ||
|
@@ -366,7 +367,6 @@ def test_gallery(self): | |
|
||
self.snapshot('gallery - text input') | ||
|
||
|
||
def test_location_link(self): | ||
app = dash.Dash(__name__) | ||
|
||
|
@@ -528,3 +528,48 @@ def update_graph(n_clicks): | |
button.click() | ||
time.sleep(2) | ||
self.snapshot('candlestick - 2 click') | ||
|
||
def test_confirm(self): | ||
app = dash.Dash(__name__) | ||
|
||
app.layout = html.Div([ | ||
html.Button(id='button', children='Send confirm', n_clicks=0), | ||
dcc.ConfirmDialog(id='confirm', message='Please confirm.'), | ||
html.Div(id='confirmed') | ||
]) | ||
|
||
@app.callback(Output('confirm', 'send_confirm'), [Input('button', 'n_clicks')]) | ||
def on_click_confirm(n_clicks): | ||
if n_clicks: | ||
return True | ||
|
||
@app.callback(Output('confirmed', 'children'), | ||
[Input('confirm', 'n_clicks'), Input('confirm', 'submit_n_clicks'), Input('confirm', 'cancel_n_clicks')],) | ||
def on_confirmed(n_clicks, submit_n_clicks, cancel_n_clicks): | ||
if not n_clicks: | ||
return | ||
if n_clicks == 1: | ||
return 'confirmed' | ||
elif n_clicks == 2: | ||
return 'canceled' | ||
|
||
self.startServer(app) | ||
button = self.wait_for_element_by_css_selector('#button') | ||
self.snapshot('confirmation -> initial') | ||
time.sleep(1) | ||
button.click() | ||
time.sleep(1) | ||
|
||
self.driver.switch_to.alert.accept() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎉 very nice! |
||
self.wait_for_text_to_equal('#confirmed', 'confirmed') | ||
|
||
self.snapshot('confirmation -> confirmed') | ||
|
||
time.sleep(0.2) | ||
button.click() | ||
time.sleep(1) | ||
self.driver.switch_to.alert.dismiss() | ||
time.sleep(0.5) | ||
self.wait_for_text_to_equal('#confirmed', 'canceled') | ||
|
||
self.snapshot('confirmation -> canceled') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Very nice tests! I'd like to see a couple of other things:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added a test for the provider and put a call count variable. The call_count get increased normally in my local tests but in circleci it get increased by 6 instead of two, I put the value to be the n_clicks instead for the tests to work on circleci but I think there's an error. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the message that will appear in the component's
help(dcc.ConfirmDialog)
, so let's expand this a bit. The Dash users probably don't know whatwindow.confirm
is referring to. Perhaps something like:And then similarly for the
ConfirmDialogProvider
. For theConfirmDialogProvider
, we should mention that you can pass in a button directly aschildren