Bring Drag & Drop to Dash with Dashboard Engine. 💫 Learn how at our next webinar!

Dynamically create callbacks not being triggered

Full working example:

import json
import uuid

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


CANDIDATES = [
    {'name': 'foobar0', 'category': 'Computers'},
    {'name': 'foobar1', 'category': 'Computers/Programming'},
    {'name': 'foobar2', 'category': 'Computers/Programming/Resources'},
    {'name': 'foobar3', 'category': 'Computers/News_and_Media'},
    {'name': 'foobar4', 'category': 'Computers/News_and_Media'},
]

CATEGORIES = {
    'Computers': {
        'parent': None,
        'id': 0,
        'children': {
            'Computers/Programming': {
                'parent': 0,
                'id': 1,
                'children': {
                    'Computers/Programming/Resources': {
                        'parent': 1,
                        'id': 2,
                        'children': [4]
                    }
                }
            },
            'Computers/News_and_Media': {
                'parent': 0,
                'id': 3,
                'children': {}
            },
        }
    }
}

app = dash.Dash()
app.config['suppress_callback_exceptions'] = True

app.layout = html.Div([
    # top controls
    html.Div(
        [
            html.Div(
                dcc.Dropdown(
                    id="root_category_dropdown",
                    options=[{'label': 'Computers', 'value': 'Computers'}]
                ),
                className="two columns"
            ),

            html.Div(
                dcc.Dropdown(
                    id="sub_category_dropdown_1",
                ),
                className="two columns",
                style={'display': 'none'},
                id="container_sub_category_dropdown_1"
            ),

            html.Div(
                dcc.Dropdown(
                    id="sub_category_dropdown_2",
                ),
                className="two columns",
                style={'display': 'none'},
                id="container_sub_category_dropdown_2"
            ),

        ],
        className="row",
        style={"marginBottom": "10"},
    ),

    # Index table
    html.Div(
        id="candidates_table",
        className="row"
    ),

    # Hidden divs to store shared data
    html.Div(
        id='selected_category',
        style={'display': 'none'}),
    html.Div(
        id='available_candidates',
        style={'display': 'none'}),
    html.Div(
        id='available_metrics',
        style={'display': 'none'}),

    # Style
    html.Link(href="https://use.fontawesome.com/releases/v5.2.0/css/all.css",rel="stylesheet"),
    html.Link(href="https://cdn.rawgit.com/plotly/dash-app-stylesheets/2d266c578d2a6e8850ebce48fdb52759b2aef506/stylesheet-oil-and-gas.css",rel="stylesheet"),
    html.Link(href="https://fonts.googleapis.com/css?family=Dosis", rel="stylesheet"),
    html.Link(href="https://fonts.googleapis.com/css?family=Open+Sans", rel="stylesheet"),
    html.Link(href="https://fonts.googleapis.com/css?family=Ubuntu", rel="stylesheet"),
    html.Link(href="https://cdn.rawgit.com/amadoukane96/8a8cfdac5d2cecad866952c52a70a50e/raw/cd5a9bf0b30856f4fc7e3812162c74bfc0ebe011/dash_crm.css", rel="stylesheet")
])


# Fill #1 options when root is picked
@app.callback(
    Output('sub_category_dropdown_1', 'options'),
    [Input('root_category_dropdown', 'value')])
def set_sub_category_dropdown_1_options(root):
    if root is None:
        return []

    category = CATEGORIES[root]
    options = [
        {'label': child.split('/')[-1], 'value': child}
        for child in category['children']
    ]
    return options

@app.callback(
    Output('sub_category_dropdown_1', 'value'),
    [Input('root_category_dropdown', 'value')])
def empty_sub_category_dropdown_1_value(root):
    return ''

# Fill #2 options when #1 is picked
@app.callback(
    Output('sub_category_dropdown_2', 'options'),
    [Input('root_category_dropdown', 'value'),
     Input('sub_category_dropdown_1', 'value'),])
def set_sub_category_dropdown_2_options(root, sub_1):
    if not sub_1 or not root:
        return []

    category = CATEGORIES[root]['children'][sub_1]
    return [
        {'label': child.split('/')[-1], 'value': child}
        for child in category['children']
    ]

@app.callback(
    Output('sub_category_dropdown_2', 'value'),
    [Input('root_category_dropdown', 'value'),
     Input('sub_category_dropdown_1', 'value'),])
def empty_sub_category_dropdown_2_value(root, sub_1):
    return ''

# Show #1 when its options are filled
@app.callback(
    Output('container_sub_category_dropdown_1', 'style'),
    [Input('sub_category_dropdown_1', 'options')])
def toggle_container_sub_category_dropdown_1(options):
    if not options:
        return {'display': 'none'}
    return {'display': 'block'}

# Show #2 when its options are filled
@app.callback(
    Output('container_sub_category_dropdown_2', 'style'),
    [Input('sub_category_dropdown_2', 'options')])
def toggle_container_sub_category_dropdown_2(options):
    if not options:
        return {'display': 'none'}
    return {'display': 'block'}


# Set selected category when root is picked
@app.callback(
    Output('selected_category', 'children'),
    [Input('root_category_dropdown', 'value'),
     Input('sub_category_dropdown_1', 'value'),
     Input('sub_category_dropdown_2', 'value')])
def set_selected_category(root, sub_cat, sub_sub_cat):
    if sub_sub_cat and sub_cat and root:
        return sub_sub_cat
    elif sub_cat and root:
        return sub_cat
    elif root:
        return root
    return 'Computers'

# Set candidates once selected category is set
@app.callback(
    Output('available_candidates', 'children'),
    [Input('selected_category', 'children')])
def set_available_candidates(selected_category):
    if selected_category:
        candidates = [c for c in CANDIDATES if c['category'].startswith(selected_category)]
        return json.dumps(candidates)
    return "{}"


def generate_callback_update_root_when_category_clicked():
    def output_callback(_, state):
        print("Dynamic callback called")
        return state
    return output_callback

def register_category_link(link_id):
    app.callback(
        Output(link_id, 'children'),
        [Input(link_id, 'n_clicks')],
        [State(link_id, 'children')])(
             generate_callback_update_root_when_category_clicked()
         )

@app.callback(
    Output('static_link_id', 'children'),
    [Input('static_link_id', 'n_clicks')],
    [State('static_link_id', 'children')])
def test_callback(_, state):
    print("Static callback called")
    return state

@app.callback(
    Output('candidates_table', 'children'),
    [Input('available_candidates', 'children')])
def build_index(available_candidates):
    candidates = json.loads(available_candidates)
    rows = []
    for candidate in candidates:
        link_id = str(uuid.uuid1())
        cells = [
            html.Td(candidate['name']),
            html.Td(html.Div(id=link_id, children=html.A(children=candidate['category']))),
            html.Td(html.Div(id='static_link_id', children=html.A(children=candidate['category']))),
        ]

        # Create dynamic callback
        register_category_link(link_id)

        rows.append(cells)

    columns = ["Name", "Dynamic Callback Category", "Static Callback Category"]
    return html.Table(
        # Header
        [html.Tr([html.Th(col) for col in columns])] +

        # Body
        [html.Tr(row) for row in rows]
    )

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

I have a three drop down that represent hierarchical categories and a list of candidates in a table that have the selected category as their category.

I would like the option to update the drop down values when I click on a candidate’s category in the table.

I set a unique dynamic ID to the anchors in my rows in my table and then create a dynamic callback for that anchor.

The issue is that the dynamic callback is never called, if you run the example above, you can click on both links in each row and notice that only the static callback prints in the console when clicked.

What am I missing?

Thanks

I’m not a Dash developer but I’m under the impression that all callbacks must be registered before app.run_server() is called.

And it looks to me that you’re trying to register new callbacks inside a callback when the server is already running.

I’m having the same issues. Did you find a solution for your problem?

Same issue here, any solution

This should now be possible with pattern matching callbacks.

Hi Adam, please help on below query.

I am not able to see a box in front of labels.