Reputation: 288
I am trying to create an app which keeps count of the number of times Button A and Button B are clicked. I am doing that by checking the values of n_click for each button and using them as inputs in call backs. What I additionally want to do is set the n_click to zero for both buttons if n_clicks of Button B exceeds n_clicks of Button A.
Is there anyway such can be achieved in Dash? Or do I need to store variables in storage and then update them everytime a button is clicked?
Edit: The accepted answer below requires Dash 1.20.0. I had to upgrade mine from 1.18.1 to 1.20.0 to get this functionality.
Upvotes: 1
Views: 6072
Reputation: 15462
The approach below works for dash
version 1.19.0 up to the current version 1.20.0 (and probably onwards).
[1.19.0] - 2021-01-19: Adds support for callbacks which have overlapping inputs and outputs. Combined with dash.callback_context this addresses many use cases which require circular callbacks.
https://github.com/plotly/dash/blob/dev/CHANGELOG.md#added-1
As you've already said you can use the n_clicks
property
of the buttons to get the number of times each button has been clicked.
n_clicks
can also be set using a regular Output
.
...Or do I need to store variables in storage and then update them everytime a button is clicked?
n_clicks
and dash.callback_context
track this for you so I don't think there is a need to track it yourself also.
In addition to event properties like n_clicks that change whenever an event happens (in this case a click), there is a global variable dash.callback_context, available only inside a callback.
https://dash.plotly.com/advanced-callbacks
Example of how you could go about it (Adjusted version of the advanced callback example in the documentation linked above):
import json
import dash
import dash_html_components as html
from dash.dependencies import Input, Output, State
app = dash.Dash(__name__)
app.layout = html.Div(
[
html.Button("Button A", id="btn-a"),
html.Button("Button B", id="btn-b"),
html.Div(id="container"),
]
)
@app.callback(
Output("container", "children"),
Output("btn-a", "n_clicks"),
Output("btn-b", "n_clicks"),
Input("btn-a", "n_clicks"),
Input("btn-b", "n_clicks"),
prevent_initial_call=True,
)
def display(n_clicks_a, n_clicks_b):
ctx = dash.callback_context
ctx_msg = json.dumps(
{"states": ctx.states, "triggered": ctx.triggered, "inputs": ctx.inputs},
indent=2,
)
if n_clicks_a is not None and n_clicks_b is not None and n_clicks_b > n_clicks_a:
return html.Pre(ctx_msg), 0, 0
return html.Pre(ctx_msg), n_clicks_a, n_clicks_b
if __name__ == "__main__":
app.run_server(debug=True)
In addition to using Input
you could also use State
to get n_clicks
if you
don't want the callback to trigger on a change of n_clicks
, but you do want access to its value.
Upvotes: 2
Reputation: 797
No need to do anything complicated, just put the n_clicks
property of the buttons as output of your callback and return zero as needed. There is nothing preventing you from using the same properties as both Input
and Output
of the same callback. Here is a minimal example:
import dash
from dash.dependencies import Input, Output
import dash_html_components as html
app = dash.Dash(__name__)
app.layout = html.Div([
html.Button('Button 1', id='btn1', n_clicks=0),
html.Button('Button 2', id='btn2', n_clicks=0),
html.Div(id='msg-container')
])
@app.callback(Output('msg-container', 'children'),
Output('btn1', 'n_clicks'),
Output('btn2', 'n_clicks'),
Input('btn1', 'n_clicks'),
Input('btn2', 'n_clicks'))
def displayClick(btn1, btn2):
if btn2 > btn1:
btn1, btn2 = 0, 0
msg = "Button 1 clicked {} times, Button 2 clicked {} times".format(btn1, btn2)
return msg, btn1, btn2
if __name__ == '__main__':
app.run_server(debug=True)
Upvotes: 1