Callbacks, Relation between State and Events - Multiple buttons

I am trying to build a dashboard, just copying one of the basic ones in Google Analytics. The aim of the game is to get a better understanding of dash.

A minimum working example.

import dash_html_components as html
from dash.dependencies import Input, Output, State, Event

app = dash.Dash('test setup')
app.layout = html.Div([
        html.Button([
            html.Div([
                html.Label('Users'),
                html.H4(10000)
            ], className='box')], id='userButton', className='button', value='Value 1'
        ),
        html.Button([
            html.Div([
                html.Label('Sessions'),
                html.H4(15000)
            ], className='box'),], id='sessionsButton', className='button', value='Value 2'
        ),
        html.Div(id='test_id')
,])

# This is very challenges occure
@app.callback(
    Output('google_markup', 'children'),
    [],
    [
        State('userButton', 'value'),
        State('sessionsButton', 'value')
    ],
    [
        Event('userButton', 'click'),
        Event('sessionsButton', 'click')]
)
def update_id(state, state1):
    return 'The Values is: %s %s' % (state, state1)

So what happens is that on click it just prints out ā€˜Value1 Value2ā€™. Which is not that surprising. What I would like it to do is

if userButton is clicked:
    output = 'Value 1'
if sessionsButton is clicked:
    output = 'Value 2'
return 'The value is: %s' % output

I hope that this makes some sense, I am trying to get data of a data frame and build a graph, what should update given the feature, and the reason I am using buttons is that I would like to have a summary on the button (if there is another approach, please let me know).

I think my confusion arises from not understanding the relations within the callback, hopefully someone can enlighten me.

Thanks,
Steffen

Hey,

Thereā€™s currently no way to determine which elements triggered an Event unfortunately, so thereā€™s no way to do this with the one callback and two Events. One way you can get around this is to create a function that has a single input, letā€™s call it do_stuff, and then use it to create separate callbacks, one for each button you want to target:

def do_stuff(value):
    return 'The value is: %s' % value

for element in  ('userButton', 'sessionsButton'):
    app.callback(Output('google_markup', 'children'), [], [State(element, 'value')], [Event(element, 'click')])(do_stuff)

This takes advantage of the fact that a decorator is simply a function that takes a function as an input, so we just invoke app.callback as a regular function rather than as a decorator.

There is also the n_click property of all elements, which you can target with an Input, which means whenever someone clicks on the element, the value will change and trigger the callback. However, that will only work initially, as you can check to see which value has n_clicks != 0. But after this itā€™s impossible to tell when was clicked as you donā€™t know which was incremented. Thereā€™s currently a PR that is trying to add support for n_clicks_previous, so if it getā€™s merged that could also solve this issue.

1 Like

Thanks a lot for the response and sorry for the first example not being able to executerā€¦ It does not seem to work.

import dash
import dash_html_components as html
from dash.dependencies import Input, Output, State, Event

app = dash.Dash('test setup')
app.layout = html.Div([
        html.Button([
            html.Div([
                html.Label('Users'),
                html.H4(10000)
            ], className='box')], id='userButton', className='button', value='Value 1'
        ),
        html.Button([
            html.Div([
                html.Label('Sessions'),
                html.H4(15000)
            ], className='box'),], id='sessionsButton', className='button', value='Value 2'
        ),
        html.Div(id='test_id')
])


def update_id(value):
    return 'The Values is: %s ' % value


for element in ('userButton', 'sessionsButton'):
    app.callback(Output('test_id', 'children'), [], [State(element, 'value')], [Event(element, 'click')])(update_id)


if __name__ == '__main__':
    app.run_server(debug=True)

Throws this

Traceback (most recent call last):
    File "demo.py", line 28, in <module>
        app.callback(Output('test_id', 'children'), [], [State(element, 'value')], [Event(element, 'click')])(update_id)
    File "/home/misterwhite/anaconda3/envs/untitled2/lib/python3.6/site-packages/dash/dash.py", line 489, in callback
        self._validate_callback(output, inputs, state, events)
    File "/home/misterwhite/anaconda3/envs/untitled2/lib/python3.6/site-packages/dash/dash.py", line 474, in _validate_callback
        output.component_property).replace('    ', ''))
dash.exceptions.CantHaveMultipleOutputs:
You have already assigned a callback to the output
with ID "test_id" and property "children". An output can only have
a single callback function. Try combining your inputs and
callback functions together into one function.

It looks like it is simultaneously, what am i doing wrong?

Ah dang, thatā€™s my bad, sorry. I keep forgetting about that limitation of Dash: an element.property can only be targeted by one callback.

Itā€™s possible thereā€™s not a way to do this that makes use of the one element. But I I may well be missing a simple workaround. A slightly hacky workaround is to just have two callbacks, that target different elements. If both elements start out empty, then the one that doesnā€™t get filled will not display, so it will appear as though there was only one element.

Maybe someone else will have another ideaā€¦

@steffenhvid there is a pull request on https://github.com/plotly/dash-html-components/pull/37 which adds an n_previous_clicks property to buttons. This allows you to determine which button was pressed. Hopefully this will be merged soon? @chriddyp

If youā€™re only interested in determining which element was most recently clicked on, see the solution in Input two or more button - How to tell which button is pressed? - #29 by chriddyp

Hi, assuming that you can only click on one button once at a time, you can use the ā€œn_clicks_timestampā€ attribute like this. :

@app.callback(
    Output('current_page', 'value'),
    [Input('buttonA', 'n_clicks_timestamp'),
     Input('buttonB', 'n_clicks_timestamp')])
def update_output(*args):
    buttons = ['buttonA', 'buttonB']

    args = [0 if i is None else i for i in args]
    if len(args) > 0:
        latest_time = max(args)
        index_of_latest_time = args.index(latest_time)
        return buttons[index_of_latest_time]

Exactly. For a long list of buttons, I use a utility function like this:

def without(remove_these_keys, original_list):
    return [i for i in original_list if i not in remove_these_keys]


def button_was_fired(button_timestamp, other_event_timestamps):
    return (
        len(without([button_timestamp], other_event_timestamps)) > 0
        and all([
            int(button_timestamp) > int(i)
            for i in without([button_timestamp], other_event_timestamps)
        ])
)

and then my callbacks are like:

@app.callback(Output(...), [Input('buttona', 'n_clicks_timestamp'), ...])
def update_output(*args):
    if button_was_fired(args[0], args):
         # the first button was fired
    elif button_was_fired(args[1], args):
         # the second button was fired
    elif button_was_fired(args[2], args):
         # the third button was fired
    # etc

How do I write PreventUpdate in this case?

For normal n_clicks, I can write

if n_clicks1 is None and n_clicks2 is None:
    raise PreventUpdate

Is there any similar syntax for using n_clicks_timestamp for PreventUpdate?

wouldnā€™t a better option here be to create a radioButton object that works exactly like dcc.RadioItems but looks like buttons. I was optimistic that dbc.ButtonGroup was going to work like this but it doesnā€™t. It just does a tad better formatting them together. My control has a ButtonGroup of 12 or 20 buttons so distinguishing against all the timestamps to see whether one or none of the buttons was pressed is a genuine headache and waste of CPU. I could use tabs but donā€™t want to. I am probably forced to use RadioItems (really donā€™t want to). I need a simple working ButtonGroup :o?