Input two or more button - How to tell which button is pressed?


#22

Hi Anton,

Ive tried several times (on mac), but I still end up with the same scenario. The components have the property n_clicks_previous but the value isn’t updated on click it is always None (even though the default is set to 0, n_clicks also starts as None). I tested it using the following:

app.layout = html.Div([
    html.Button('test', id='test'),
    html.Div(id='clicksDiv')
])

@app.callback(Output('clicksDiv', 'children'),
              [Input('test', 'n_clicks')],
               [State('test', 'n_clicks_previous')])
def show_clicks(n_clicks, n_clicks_previous):

    return 'clicks: {0} previous: {1}'.format(n_clicks, n_clicks_previous)

Will you be making a pull request to get this merged into master? If you do I can use the official build. I would like to know why I can’t get it working though!

Thanks


#23

happy to do PR once I am back from trip in a couple of days


#24

Any progress on this?


#25

hi, it would appear that @rmarren1 commited this change 3 days ago -

one thing that I forgot to mention in my instruction (which is critical to build and install python egg file locally) (and having forgotten i spent last hour trying to recover that knowledge!) is that you need to run in the end:

npm run install-local

this needs to be run with su priviledge. this command will take results of npm install, wrap them into egg file, deinstall previous version of dash-html-components and install a modified one.

or wait till @chriddyp reviews the change and releases new version. hope wont take long.


#26

My solution to this is to maintain click state for all buttons in a hidden div “clicked-button” then use this to determine which button was last clicked. The example below has three buttons delete, add and toggle.

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_table_experiments as dt

app = dash.Dash()

app.layout = html.Div([
    html.Div([
        html.Button(id='add-button', children='Add', n_clicks=0),
        html.Button(id='del-button', children='Delete', n_clicks=0),
        html.Button(id='tog-button', children='Toggle', n_clicks=0),
        html.Div(id='clicked-button', children='del:0 add:0 tog:0 last:nan', style={'display': 'none'})
    ]),
    html.Div(id='display-clicked', children=""),
])

@app.callback(
    dash.dependencies.Output('display-clicked', 'children'),
    [dash.dependencies.Input('clicked-button', 'children')]

)
def button_action(clicked):

    last_clicked = clicked[-3:]

    if last_clicked == 'del':
        return "You clicked delete"
    if last_clicked == 'add':
        return "You clicked add"
    if last_clicked == 'tog':
        return "You clicked toggle"


@app.callback(
    dash.dependencies.Output('clicked-button', 'children'),
    [dash.dependencies.Input('del-button', 'n_clicks'),
     dash.dependencies.Input('add-button', 'n_clicks'),
     dash.dependencies.Input('tog-button', 'n_clicks')],
    [dash.dependencies.State('clicked-button', 'children')]
)
def updated_clicked(del_clicks, add_clicks, tog_clicks, prev_clicks):

    prev_clicks = dict([i.split(':') for i in prev_clicks.split(' ')])
    last_clicked = 'nan'

    if del_clicks > int(prev_clicks['del']):
        last_clicked = 'del'
    elif add_clicks > int(prev_clicks['add']):
        last_clicked = 'add'
    elif tog_clicks > int(prev_clicks['tog']):
        last_clicked = 'tog'

    cur_clicks = 'del:{} add:{} tog:{} last:{}'.format(del_clicks, add_clicks, tog_clicks, last_clicked)

    return cur_clicks


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

#27

Thank you for this maral! I was trying to get something like this working using a similar strategy but didn’t realize that the State was different than the Input nor that the children had to be in string format!

Also see that you initialized n_clicks = 0, was having problems with “None” as the first entry. Lots to learn from this going forward!


#28

I have a prerelease PR for this available in https://github.com/plotly/dash-html-components/pull/45. If it passes review, it should be released this week.


#29

I’ve published the new versions now, here’s a quick example:

# Determine which input changed, or which button was clicked, using
# the latest functionality in dash-html-components (https://github.com/plotly/dash-html-components/pull/45)
import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
import datetime
import json

app = dash.Dash()

app.layout = html.Div([
    html.Button('Button 1', id='btn-1', n_clicks_timestamp='0'),
    html.Button('Button 2', id='btn-2', n_clicks_timestamp='0'),
    html.Button('Button 3', id='btn-3', n_clicks_timestamp='0'),
    html.Div(id='container')
])


@app.callback(Output('container', 'children'),
              [Input('btn-1', 'n_clicks_timestamp'),
               Input('btn-2', 'n_clicks_timestamp'),
               Input('btn-3', 'n_clicks_timestamp')])
def display(btn1, btn2, btn3):
    if int(btn1) > int(btn2) and int(btn1) > int(btn3):
        msg = 'Button 1 was most recently clicked'
    elif int(btn2) > int(btn1) and int(btn2) > int(btn3):
        msg = 'Button 2 was most recently clicked'
    elif int(btn3) > int(btn1) and int(btn3) > int(btn2):
        msg = 'Button 3 was most recently clicked'
    else:
        msg = 'None of the buttons have been clicked yet'
    return html.Div([
        html.Div('btn1: {}'.format(btn1)),
        html.Div('btn2: {}'.format(btn2)),
        html.Div('btn3: {}'.format(btn3)),
        html.Div(msg)
    ])

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

dash-determine-which-button-was-clicked

To use, upgrade to the latest dash-html-components and dash-renderer: https://dash.plot.ly/installation

The n_clicks_timestamp property is available for all elements in dash-html-components. We still need a solution that’s generally available for _all_components and all properties, but this is a start.

Thanks for the feedback everyone!


Callbacks, Relation between State and Events - Multiple buttons
Multipage app Broken!
#30

Thank you very much. The n_clicks_timestamp solved my problem too.


#31

Thanks for doing this change! finally i can upgrade to the latest dash-html-components.
One comment though, this approach is inferior to using n_clicks_previous. If you combine multiple n_clicks, n_clicks_timestamp and other inputs, then on other inputs change you wont be able to say whether button was clicked or not. Timestamps will still indicate which button was clicked latest, but change could come from other input. n_clicks_previous was very deterministic and would allow to to positively say that change in the input came from button and which one.

still, for most of the purposes n_clicks_timestamp works.


#32

Sir, I can’t use n_clicks_time-stamp for other html components like, radio button or slide bar. It shows error-
TypeError: Unexpected keyword argument `n_clicks_timestamp’
Sorry sir, Issue resolved , I am trying it on dash_core_components. My bad


#33

Hope this will help

first put the n_timestamp value like this in button property

html.Button(
                [
html.I(className="fas fa-download margin_right", style={"margin": "0 13% 0 0"}), "CSV"],
                    className="btn btn-danger",
                    id="attribute-download_csv",
                    n_clicks_timestamp='0',
                    style={
                        "width": "81%"
                    }
                ),

@app.callback(
  Output('query_content', 'children'),
  [
        Input('phase_btn', 'n_clicks_timestamp'),
        Input('attribute_btn', 'n_clicks_timestamp'),
        Input('query_btn', 'n_clicks_timestamp'),
        Input('trends_chart', 'n_clicks_timestamp')
  ],
  state=[State("query_generated_text_box", "value")]
)
def display(btn1, btn2, btn3, btn4, query):
    btn_state = [int(btn1), int(btn2), int(btn3), int(btn4)]
    if all(v == 0 for v in btn_state):
        #it will be fired on th page load. 
        return "I was fired on page load"
    max_index = btn_state.index(max(i for i in btn_state if i is not None))
    if max_index == 0:
        return "one was clicked"
    elif max_index == 1:
        return "two was clicked"
    elif max_index == 2:
            return html.Div("Three was clicked)
    elif max_index == 3:
        return ("fouth was clicked")

To prevent the first callback use

raise dash.exceptions.PreventUpdate()

instead of

        #it will be fired on th page load. 
        return "I was fired on page load"