I want to stop the user from pressing the "Submit" button for 30 seconds, after they have pushed it in the script below. How would I go about doing this? This is how my code looks currently:
import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash()
app.layout = html.Div([
dcc.Input(id='my-id', value='initial value', type="text"),
html.Button('Submit', id='button'),
html.Div(id='my-div')
])
@app.callback(
Output(component_id='my-div', component_property='children'),
[Input('button', 'n_clicks')],
state=[State(component_id='my-id', component_property='value')]
)
def update_output_div(n_clicks, input_value):
return 'You\'ve entered "{}" and clicked {} times'.format(input_value, n_clicks)
if __name__ == '__main__':
app.run_server()
Does anyone know how I can stop users from pushing the button for 30 seconds?
Thank in advance.
EDIT 15/08/2018 9:30AM GMT RESPONSE TO stevepastelan:
import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash()
app.layout = html.Div([
dcc.Input(id='my-id', value='initial value', type="text"),
html.Button('Submit', id='button'),
html.Div([dcc.Interval(
id='interval-component',
interval=1 * 3000, # in milliseconds
n_intervals=0
)]),
html.Div(id='my-div')
])
@app.callback(
Output(component_id='my-div', component_property='children'),
[Input('button', 'n_clicks')], [Input('interval-component', 'n_intervals')],
state=[State(component_id='my-id', component_property='value')]
)
def update_output_div(n_clicks,n_intervals, input_value):
return 'You\'ve entered "{}" and clicked {} times'.format(input_value, n_clicks)
if __name__ == '__main__':
app.run_server()
EDIT 15/08/2018 16:22PM WROTE SIMPLER SCRIPT WITH THE AN EDITED CALLBACK BUT IT DOESNT WORK:
import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash()
app.layout = html.Div([
dcc.Input(id='my-id', value='initial value', type="text"),
html.Button('Submit', id='button'),
html.Div([dcc.Interval(
id='interval-component',
interval=1 * 3000, # in milliseconds
n_intervals=0
)]),
html.Div(id='my-div')
])
@app.callback(
Output(component_id='my-div', component_property='children'),
[Input('button', 'n_clicks')], [Input('interval-component', 'n_intervals')],
state=[State(component_id='my-id', component_property='value')]
)
def update_output_div(n_clicks,n_intervals, input_value):
return 'You\'ve entered "{}" and clicked {} times'.format(input_value, n_clicks)
if __name__ == '__main__':
app.run_server()
Okay, I managed to implement my own suggestion, but it was not trivial and still has quirks.
Complicating factors were:
Output
Input
or Event
triggered your callback. Workarounds generally involve tracking the number of clicks per button (see https://github.com/plotly/dash-html-components/pull/37 as an example).disable=True
or max_requests=0
appears to be permanent. Once I stopped a timer in this way, I could not restart it with either disable=False
or max_requests=1000
.Issues:
update_output_div()
gets called twice -- but you can tell the difference between the two by measuring the number of button clicks to the previous count, so you can keep it from submitting your data twice.100
and (1000 * BUTTON_PRESS_LOCKOUT_SECONDS)-100
as the two timer durations. In principle, you could split them evenly half and half. I don't know if there are any problems with using a low timeout when working over the network (I did my testing on localhost).Inspiration drawn from:
import json
import datetime
import dash
from dash.dependencies import Input, Output, State, Event
import dash_core_components as dcc
import dash_html_components as html
BUTTON_PRESS_LOCKOUT_SECONDS = 10 # seconds
app = dash.Dash()
app.config['suppress_callback_exceptions']=True
def serve_layout():
return html.Div([
dcc.Input(id='my-id', value='initial value', type="text"),
html.Button('Submit', id='button'),
html.Div(
[
dcc.Interval(id='interval-component', disabled=True)
, dcc.Interval(id='interval-sync-component', disabled=True)
]
, id='interval-container'
),
html.Div("", id='my-div'),
html.Div(json.dumps({'n_clicks':0, 'n_previous_clicks':0}), id='local_data'),
html.Div('??', id='button-status'),
])
app.layout = serve_layout
# Track button clicks
@app.callback(
output=Output(component_id='local_data', component_property='children'),
inputs=[Input('button', 'n_clicks')],
state=[State('local_data', 'children')],
events=[Event('interval-sync-component', 'interval')]
)
def track_clicks(n_clicks, local_data_json):
if n_clicks is None:
n_clicks = 0
local_data = json.loads(local_data_json)
n_previous_clicks = local_data['n_clicks']
# Update local data with the new click data
local_data.update(**{'n_clicks': n_clicks, 'n_previous_clicks': n_previous_clicks})
# local_data.update(**{'n_clicks': n_clicks, 'n_previous_clicks': n_previous_clicks})
return json.dumps(local_data)
# When the button click count is updated, submit
@app.callback(
output=Output(component_id='my-div', component_property='children'),
inputs=[Input('local_data', 'children')],
state=[State(component_id='my-id', component_property='value'), State('my-div', 'children')]
)
def update_output_div(local_data_json, input_value, current_state):
local_data = json.loads(local_data_json)
n_clicks = local_data['n_clicks']
n_previous_clicks = local_data['n_previous_clicks']
# Real submit
if n_clicks > n_previous_clicks:
return 'You\'ve entered "{}" and clicked {} times ({})'.format(
input_value
, n_clicks if n_clicks is not None else 0
, datetime.datetime.now()
)
# Not a real submit, but function is called an extra time as a side effect of the timer nonsense below.
else:
return '*' + current_state
# Start (or stop) the timer
@app.callback(
output=Output('interval-container', 'children'),
inputs=[Input('local_data', 'children')],
state=[State('button', 'disabled')],
events=[Event('interval-component', 'interval')]
)
def start_timer(local_data_json, button_is_disabled):
local_data = json.loads(local_data_json)
n_clicks = local_data['n_clicks']
n_previous_clicks = local_data['n_previous_clicks']
children=[]
if n_clicks > n_previous_clicks:
sync_timer = dcc.Interval(
id='interval-sync-component',
interval=100, # in milliseconds
)
children.append(sync_timer)
if button_is_disabled:
main_timer = dcc.Interval(
id='interval-component',
interval=(1000 * BUTTON_PRESS_LOCKOUT_SECONDS)-100, # in milliseconds
)
children.append(main_timer)
return children
# Enable the button whenever the timer interval is triggered or disable it when the button is pressed
@app.callback(
output=Output('button', 'disabled'),
inputs=[Input('button', 'n_clicks')],
state=[State('local_data', 'children')],
events=[Event('interval-component', 'interval')]
)
def toggle_button_disabled_state(n_clicks, local_data_json):
local_data = json.loads(local_data_json)
# n_clicks = local_data['n_clicks']
if n_clicks is None:
n_clicks = 0
n_previous_clicks = local_data['n_previous_clicks']
# We got here via button click, so disable the button
if n_clicks > n_previous_clicks:
return True
# We got here via timer expiration, so enable the button
else:
return False # enable the button
# Report on the button status
@app.callback(
output=Output('button-status', 'children'),
inputs=[Input('button', 'disabled')]
)
def update_button_status(disabled):
if disabled:
return 'Disabled submit button for {} seconds'.format(BUTTON_PRESS_LOCKOUT_SECONDS)
else:
return 'Submit button enabled'
if __name__ == '__main__':
app.run_server()
You can trigger actions to be taken on the page based on a timer using dash_core_components.Interval
. There are some examples here: https://dash.plot.ly/live-updates
You could initialize your interval component with n_intervals = 0
, and then make your submit button disable itself and set n_intervals = 1
. Then write a callback on the interval that re-enables the button.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With