How can I use ctx.triggered w/ an ALL callback to identify a button

TL;DR

My python-fu is too weak to figure out how to retrieve my identifier from the “stringified dictionary with no whitespace” that is stored in dash.callback_context.triggered for a MATCH input.

E.g. given that it contains

{"id":"example_input.csv","type":"job-delete-button"}.n_clicks

I’d like get my hands on example_input.csv.

Best I can figure is to clip the .n_clicks off the end and feed the remainder to json.loads.

That’s leaving me feeing kind of dirty. Is there something more proper?

I’ve also discovert ast.literal_eval as a possible solution. Is that any cleaner?


Details

I have a list of jobs in a dcc.Store. I have a callback (update_jobs) that manages them. One input to that callback is a dcc.Upload that adds the new info to the jobs list and returns it as output.

I have callback (update_job_list) that takes the jobs list’s data as input and generates a bunch of html.P’s that are output into a div.

That all works fine.

I’ve extended update_job_list so that each P element also contains a button for the corresponding job, defined like so:

   158         delete_button = html.Button(
   159             id={"type": "job-delete-button", "id": j["filename"]}
   160         )

where j["filename"] is a unique identifier for that button.

I’ve add an input and matching parameter to update_jobs like so:

   118         Input({"type": "job-delete-button", "id": ALL}, "n_clicks"),

and whenever I click the button the update_jobs callback triggers.

BUT I’m at a loss as to how to identify which button was pressed.

The prop_id field of dash.callback_context.triggered contains:

{"id":"example_input.csv","type":"job-delete-button"}.n_clicks

which the docs say is “a stringified dictionary with no whitespace”.

The only example that I can find is the ToDo app on the Pattern matching callbacks page, but the inputs it’s dealing with aren’t pattern matched.

How should I retrieve example_input.csv from the string that’s store in triggered? All of the solutions I’ve come up with are kind of whacky, e.g. trim .n_clicks off the tail and then feed the string to json.loads.

What’s The Right Way™?

ps. Thanks for making it this far…

I would say that what you are doing is correct. Is it beautiful? Maybe not, but you can always hide it in a utility function. Mine (implemented in the dash-extensions package, available on pip) has a syntax like this,

import dash
import dash_html_components as html
from dash.dependencies import ALL, Input, Output
from dash_extensions.callback import get_trigger

app = dash.Dash(prevent_initial_callbacks=True)
app.layout = html.Div([html.Button("Click me", id={"type": "btn", "index": "myindex"}), html.Div(id="out")])


@app.callback(Output("out", "children"), [Input({"type": "btn", "index": ALL}, "n_clicks")])
def func(n_clicks):
    trigger = get_trigger()
    return trigger.id["index"]


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

But under the hood, i am doing more-or-less what you are suggesting in the first place.

@emilThanks!, it’s nice to know that I’m not alone.

I think that your get_trigger routine isn’t completely safe; simply splitting on a “.” and taking the first bit that was returned won’t work if there’ are other . in the trigger string (e.g. my example contains a filename like foo.csv).

Indexing in from the right is probably safer, but…

Haha, good catch! I simply assumed that the id would not contain a ‘.’ (i thought this was a reasonable assumption), but your suggestion would indeed make the code more robust :blush: