Pattern-Matching Callbacks

New in Dash 1.11.0!

The pattern-matching callback selectors MATCH, ALL, & ALLSMALLER
allow you to write callbacks that respond to or update an
arbitrary or dynamic number of components.

Simple Example with ALL

This example renders an arbitrary number of dcc.Dropdown elements
and the callback is fired whenever any of the dcc.Dropdown elements
change. Try adding a few dropdowns and selecting their values to see how
the app updates.

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State, MATCH, ALL

app = dash.Dash(__name__, suppress_callback_exceptions=True)

app.layout = html.Div([
    html.Button("Add Filter", id="add-filter", n_clicks=0),
    html.Div(id='dropdown-container', children=[]),
    html.Div(id='dropdown-container-output')
])

@app.callback(
    Output('dropdown-container', 'children'),
    [Input('add-filter', 'n_clicks')],
    [State('dropdown-container', 'children')])
def display_dropdowns(n_clicks, children):
    new_dropdown = dcc.Dropdown(
        id={
            'type': 'filter-dropdown',
            'index': n_clicks
        },
        options=[{'label': i, 'value': i} for i in ['NYC', 'MTL', 'LA', 'TOKYO']]
    )
    children.append(new_dropdown)
    return children

@app.callback(
    Output('dropdown-container-output', 'children'),
    [Input({'type': 'filter-dropdown', 'index': ALL}, 'value')]
)
def display_output(values):
    return html.Div([
        html.Div('Dropdown {} = {}'.format(i + 1, value))
        for (i, value) in enumerate(values)
    ])


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

Some notes about this example:
- Notice how the id in dcc.Dropdown is a dictionary rather than a string.
This is a new feature that we enabled for pattern-matching callbacks
(previously, IDs had to be strings).
- In our second callback, we have Input({'type': 'dropdown', 'index': ALL}, 'value').
This means “match any input that has an ID dictionary where 'type' is 'dropdown'
and 'index' is anything. Whenever the value property of any of the
dropdowns change, send all of their values to the callback.”
- The keys & values of the ID dictionary (type, index, filter-dropdown)
are arbitrary. This could’ve be named {'foo': 'bar', 'baz': n_clicks}.
- However, for readability, we recommend using keys like type, index, or id.
type can be used to refer to the class or set dynamic components and
index or id could be used to refer which component you are matching
within that set. In this example, we just have a single set of dynamic
components but you may have multiple sets of dynamic components in more
complex apps or if you are using MATCH (see below).
- In fact, in this example, we didn’t actually need 'type': 'filter-dropdown'.
The same callback would have worked with Input({'index': ALL}, 'value').
We included 'type': 'filter-dropdown' as an extra specifier in case you
create multiple sets of dynamic components.
- The component properties themselves (e.g. value) cannot be matched by
a pattern, only the IDs are dynamic.
- This example uses a common pattern with State - the currently displayed
set of dropdowns within the dropdown-container component are passed into
the callback when the button is clicked. Within the callback, the new
dropdown is appended to the list and then returned.
- You can also use dash.callback_context to access the inputs and state
and to know which input changed.
Here is what that data might look like with two dropdowns rendered on the page.
- dash.callback_context.triggered. Note that the prop_id is a stringified dictionary with no whitespace.
[ { 'prop_id': '{"index":0,"type":"filter-dropdown"}.value', 'value': 'NYC' } ]
- dash.callback_context.inputs. Note that the key is a stringified dictionary with no whitespace.
{ '{"index":0,"type":"filter-dropdown"}.value': 'NYC', '{"index":1,"type":"filter-dropdown"}.value': 'LA' }
- dash.callback_context.inputs_list. Each element of the list corresponds to
one of the input declarations. If one of the input declarations matches a
pattern then it will contain a list of values.
[ [ { 'id': { 'index': 0, 'type': 'filter-dropdown' }, 'property': 'value', 'value': 'NYC' }, { 'id': { 'index': 1, 'type': 'filter-dropdown' }, 'property': 'value', 'value': 'LA' } ] ]
- dash.callback_context.outputs_list
{ 'id': 'dropdown-container-output', 'property': 'children' }

Simple Example with MATCH

Like ALL, MATCH will fire the callback when any of the
component’s properties change. However, instead of passing all of the
values into the callback, MATCH will pass just a single value into the
callback. Instead of updating a single output, it will update the dynamic
output that is “matched” with.

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State, MATCH, ALL

app = dash.Dash(__name__, suppress_callback_exceptions=True)

app.layout = html.Div([
    html.Button("Add Filter", id="dynamic-add-filter", n_clicks=0),
    html.Div(id='dynamic-dropdown-container', children=[]),
])

@app.callback(
    Output('dynamic-dropdown-container', 'children'),
    [Input('dynamic-add-filter', 'n_clicks')],
    [State('dynamic-dropdown-container', 'children')])
def display_dropdowns(n_clicks, children):
    new_element = html.Div([
        dcc.Dropdown(
            id={
                'type': 'dynamic-dropdown',
                'index': n_clicks
            },
            options=[{'label': i, 'value': i} for i in ['NYC', 'MTL', 'LA', 'TOKYO']]
        ),
        html.Div(
            id={
                'type': 'dynamic-output',
                'index': n_clicks
            }
        )
    ])
    children.append(new_element)
    return children


@app.callback(
    Output({'type': 'dynamic-output', 'index': MATCH}, 'children'),
    [Input({'type': 'dynamic-dropdown', 'index': MATCH}, 'value')],
    [State({'type': 'dynamic-dropdown', 'index': MATCH}, 'id')],
)
def display_output(value, id):
    return html.Div('Dropdown {} = {}'.format(id['index'], value))


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

Notes about this example:
- The display_dropdowns callback returns two elements with the same
index: a dropdown and a div.
- The second callback uses the MATCH selector. With this selector,
we’re asking Dash to:

  1. Fire the callback whenever the value property of any component
    with the id 'type': 'dynamic-dropdown' changes:
    Input({'type': 'dynamic-dropdown', 'index': MATCH}, 'value')
  2. Update the component with the id 'type': 'dynamic-output'
    and the index that matches the same index of the input:
    Output({'type': 'dynamic-output', 'index': MATCH}, 'children')
  3. Pass along the id of the dropdown into the callback:
    State({'type': 'dynamic-dropdown', 'index': MATCH}, 'id')
    - With the MATCH selector, only a single value is passed into the callback
    for each Input or State. This is unlike the previous example with the
    ALL selector where Dash passed all of the values into the callback.
    - Notice how it’s important to design IDs dictionaries that “line up” the
    inputs with outputs. The MATCH contract is that Dash will update
    whichever output has the same dynamic ID as the id. In this case, the
    “dynamic ID” is the value of the index and we’ve designed our layout to
    return dropdowns & divs with identical values of index.
    - In some cases, it may be important to know which dynamic component changed.
    As above, you can access this by setting id as State in the callback.
    - You can also use dash.callback_context to access the inputs and state
    and to know which input changed. outputs_list is particularly useful with
    MATCH because it can tell you which dynamic component this particular
    invocation of the callback is responsible for updating.
    Here is what that data might look like with two dropdowns rendered on the page after
    we change the first dropdown.
    - dash.callback_context.triggered. Note that the prop_id is a stringified dictionary with no whitespace.
    [ { 'prop_id': '{"index":0,"type":"dynamic-dropdown"}.value', 'value': 'NYC' } ]
    - dash.callback_context.inputs. Note that the key is a stringified dictionary with no whitespace.
    { '{"index":0,"type":"dynamic-dropdown"}.value': 'NYC' }
    - dash.callback_context.inputs_list. Each element of the list corresponds to
    one of the input declarations. If one of the input declarations matches a
    pattern then it will contain a list of values.
    [ [ { 'id': { 'index': 0, 'type': 'dynamic-dropdown' }, 'property': 'value', 'value': 'NYC' } ] ]
    - dash.callback_context.outputs_list
    { 'id': { 'index': 0, 'type': dynamic-output' }, 'property': 'children' }

Simple Example with ALLSMALLER

In the example below, ALLSMALLER is used to pass in the values of
all of the dropdowns on the page that have an index smaller than the
index corresponding to the div.

The user interface in the example below displays filter results that are
increasingly specific in each as we apply each additional dropdown.

ALLSMALLER can only be used in Input and State items, and
must be used on a key that has MATCH in the Output item(s).

ALLSMALLER it isn’t always necessary (you can usually use ALL and
filter out the indices in your callback) but it will make your logic simpler.

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State, MATCH, ALL, ALLSMALLER
import pandas as pd

df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv')

app = dash.Dash(__name__, suppress_callback_exceptions=True)

app.layout = html.Div([
    html.Button('Add Filter', id='add-filter-ex3', n_clicks=0),
    html.Div(id='container-ex3', children=[]),
])

@app.callback(
    Output('container-ex3', 'children'),
    [Input('add-filter-ex3', 'n_clicks')],
    [State('container-ex3', 'children')])
def display_dropdowns(n_clicks, existing_children):
    existing_children.append(html.Div([
        dcc.Dropdown(
            id={
                'type': 'filter-dropdown-ex3',
                'index': n_clicks
            },
            options=[{'label': i, 'value': i} for i in df['country'].unique()],
            value=df['country'].unique()[n_clicks]
        ),
        html.Div(id={
            'type': 'output-ex3',
            'index': n_clicks
        })
    ]))
    return existing_children


@app.callback(
    Output({'type': 'output-ex3', 'index': MATCH}, 'children'),
    [Input({'type': 'filter-dropdown-ex3', 'index': MATCH}, 'value'),
     Input({'type': 'filter-dropdown-ex3', 'index': ALLSMALLER}, 'value')],
)
def display_output(matching_value, previous_values):
    previous_values_in_reversed_order = previous_values[::-1]
    all_values = [matching_value] + previous_values_in_reversed_order

    dff = df[df['country'].str.contains('|'.join(all_values))]
    avgLifeExp = dff['lifeExp'].mean()

    # Return a slightly different string depending on number of values
    if len(all_values) == 1:
        return html.Div('{:.2f} is the life expectancy of {}'.format(
            avgLifeExp, matching_value
        ))
    elif len(all_values) == 2:
        return html.Div('{:.2f} is the average life expectancy of {}'.format(
            avgLifeExp, ' and '.join(all_values)
        ))
    else:
        return html.Div('{:.2f} is the average life expectancy of {}, and {}'.format(
            avgLifeExp, ', '.join(all_values[:-1]), all_values[-1]
        ))

if __name__ == '__main__':
    app.run_server(debug=True)
  • In the example above, try adding a few filters and then change the first
    dropdown. Notice how changing this dropdown will update the text
    of each html.Div that has an index that depends on that dropdown.
  • That is, each html.Div will get updated whenever any of the
    dropdowns with an index smaller than it has changed.
  • So, if there are 10 filters added and the first dropdown has changed, Dash
    will fire your callback 10 times, once to update each html.Div that depends
    on the dcc.Dropdown that changed.
  • As above, you can also use dash.callback_context to access the inputs and state
    and to know which input changed.
    Here is what that data might look like when updating the second div
    with two dropdowns rendered on the page after we change the first dropdown.
  • dash.callback_context.triggered. Note that the prop_id is a stringified dictionary with no whitespace.
    [ { 'prop_id': '{"index":0,"type":"filter-dropdown-ex3"}.value', 'value': 'Canada' } ]
  • dash.callback_context.inputs. Note that the key is a stringified dictionary with no whitespace.
    { '{"index":1,"type":"filter-dropdown-ex3"}.value': 'Albania', '{"index":0,"type":"filter-dropdown-ex3"}.value': 'Canada' }
  • dash.callback_context.inputs_list. Each element of the list corresponds to
    one of the input declarations. If one of the input declarations matches a
    pattern then it will contain a list of values.
    [ { 'id': { 'index': 1, 'type': 'filter-dropdown-ex3' }, 'property': 'value', 'value': 'Albania' }, [ { 'id': { 'index': 0, 'type': 'filter-dropdown-ex3' }, 'property': 'value', 'value': 'Canada' } ] ]
  • dash.callback_context.outputs_list
    { 'id': { 'index': 1, 'type': output-ex3' }, 'property': 'children' }

Todo App

Creating a Todo App is a classic UI exercise in that demonstrates many
features in common “create, read, update and delete” (CRUD) applications.

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State, MATCH, ALL

app = dash.Dash(__name__)

app.layout = html.Div([
    html.Div('Dash To-Do list'),
    dcc.Input(id="new-item"),
    html.Button("Add", id="add"),
    html.Button("Clear Done", id="clear-done"),
    html.Div(id="list-container"),
    html.Div(id="totals")
])

style_todo = {"display": "inline", "margin": "10px"}
style_done = {"textDecoration": "line-through", "color": "#888"}
style_done.update(style_todo)


@app.callback(
    [
        Output("list-container", "children"),
        Output("new-item", "value")
    ],
    [
        Input("add", "n_clicks"),
        Input("new-item", "n_submit"),
        Input("clear-done", "n_clicks")
    ],
    [
        State("new-item", "value"),
        State({"index": ALL}, "children"),
        State({"index": ALL, "type": "done"}, "value")
    ]
)
def edit_list(add, add2, clear, new_item, items, items_done):
    triggered = [t["prop_id"] for t in dash.callback_context.triggered]
    adding = len([1 for i in triggered if i in ("add.n_clicks", "new-item.n_submit")])
    clearing = len([1 for i in triggered if i == "clear-done.n_clicks"])
    new_spec = [
        (text, done) for text, done in zip(items, items_done)
        if not (clearing and done)
    ]
    if adding:
        new_spec.append((new_item, []))
    new_list = [
        html.Div([
            dcc.Checklist(
                id={"index": i, "type": "done"},
                options=[{"label": "", "value": "done"}],
                value=done,
                style={"display": "inline"},
                labelStyle={"display": "inline"}
            ),
            html.Div(text, id={"index": i}, style=style_done if done else style_todo)
        ], style={"clear": "both"})
        for i, (text, done) in enumerate(new_spec)
    ]
    return [new_list, "" if adding else new_item]


@app.callback(
    Output({"index": MATCH}, "style"),
    [Input({"index": MATCH, "type": "done"}, "value")]
)
def mark_done(done):
    return style_done if done else style_todo


@app.callback(
    Output("totals", "children"),
    [Input({"index": ALL, "type": "done"}, "value")]
)
def show_totals(done):
    count_all = len(done)
    count_done = len([d for d in done if d])
    result = "{} of {} items completed".format(count_done, count_all)
    if count_all:
        result += " - {}%".format(int(100 * count_done / count_all))
    return result


if __name__ == "__main__":
    app.run_server(debug=True)
Dash To-Do list