Updating a dropdown menu's contents dynamically


#1

It seems that dropdown menus are used exclusively as inputs to other dash objects. Or at least this is the case in the examples. In the interactive section of the “getting started” guide, you get to select a country from the dropdown menu, and then the graph updates based on the country you’ve selected.

What if I want to update another dropdown menu? Would I use a callback to update the ‘options’ property of the child-dropdown? This doesn’t seem to work. Below is some code to see this. There are two dropdown menus.

The behavior I would expect is to see: The parent dropdown gets populated as normal (with names Chris and Jack), and selecting one of the names should update the options of the child dropdown.

For example, when chriddy is selected in the parent dropdown, the optn_c options should be available in the child dropdown, and when jackp is selected in the parent dropdown, the optn_j options should be available in the child dropdown.

The behavior that I see: The parent dropdown menu gets populated. Population order is random, since the data type is Dict. Assuming chriddy is the first item to appear in the parent dropdown, then the child dropdown gets populated with the ‘c’ options: 'opt1_c', 'opt2_c', 'opt3_c'. But when I choose the jackp from the parent dropdown, the ‘j’ options don’t show up in the second dropdown menu.

What am I doing wrong? This updating of dropdown lists in a chain would be very useful, as it naturally translates into SQL-like queries.

import dash
import dash_core_components as dcc
import dash_html_components as html

app = dash.Dash()

fnameDict = {'chriddy': ['opt1_c', 'opt2_c', 'opt3_c'], 'jackp': ['opt1_j', 'opt2_j']}

names = list(fnameDict.keys())
nestedOptions = fnameDict[names[0]]

app.layout = html.Div(
    [
        html.Div([
        dcc.Dropdown(
            id='name-dropdown',
            options=[{'label':name, 'value':name} for name in names],
            value = list(fnameDict.keys())[0]
            ),
            ],style={'width': '20%', 'display': 'inline-block'}),
        html.Div([
        dcc.Dropdown(
            id='opt-dropdown',
            options=[{'label':opt, 'value':opt} for opt in nestedOptions],
            value = nestedOptions[0]
            ),
            ],style={'width': '20%', 'display': 'inline-block'}
        ),
    ]
)

@app.callback(
    dash.dependencies.Output('opt-dropdown', 'options'),
    [dash.dependencies.Input('name-dropdown', 'value')]
)
def update_date_dropdown(name):
    opts = fnameDict[name]
    options=[{'label':opt, 'value':opt} for opt in opts]
    return {'options':options}

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

#2

This should work.

import dash
import dash_core_components as dcc
import dash_html_components as html

app = dash.Dash()

fnameDict = {'chriddy': ['opt1_c', 'opt2_c', 'opt3_c'], 'jackp': ['opt1_j', 'opt2_j']}

names = list(fnameDict.keys())
nestedOptions = fnameDict[names[0]]

app.layout = html.Div(
    [
        html.Div([
        dcc.Dropdown(
            id='name-dropdown',
            options=[{'label':name, 'value':name} for name in names],
            value = list(fnameDict.keys())[0]
            ),
            ],style={'width': '20%', 'display': 'inline-block'}),
        html.Div([
        dcc.Dropdown(
            id='opt-dropdown',
            ),
            ],style={'width': '20%', 'display': 'inline-block'}
        ),
        html.Hr(),
        html.Div(id='display-selected-values')
    ]
)

@app.callback(
    dash.dependencies.Output('opt-dropdown', 'options'),
    [dash.dependencies.Input('name-dropdown', 'value')]
)
def update_date_dropdown(name):
    return [{'label': i, 'value': i} for i in fnameDict[name]]

@app.callback(
    dash.dependencies.Output('display-selected-values', 'children'),
    [dash.dependencies.Input('opt-dropdown', 'value')])
def set_display_children(selected_value):
    return 'you have selected {} option'.format(selected_value)



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

#3

Works!

Now I see what I did wrong too!


#4

Thanks @raghunath!

Note that a similar example was posted in the user guide: https://plot.ly/dash/getting-started-part-2, code copied below:

# -*- coding: utf-8 -*-
import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html

app = dash.Dash(__name__)

all_options = {
    'America': ['New York City', 'San Francisco', 'Cincinnati'],
    'Canada': [u'Montréal', 'Toronto', 'Ottawa']
}
app.layout = html.Div([
    dcc.RadioItems(
        id='countries-dropdown',
        options=[{'label': k, 'value': k} for k in all_options.keys()],
        value='America'
    ),

    html.Hr(),

    dcc.RadioItems(id='cities-dropdown'),

    html.Hr(),

    html.Div(id='display-selected-values')
])

@app.callback(
    dash.dependencies.Output('cities-dropdown', 'options'),
    [dash.dependencies.Input('countries-dropdown', 'value')])
def set_cities_options(selected_country):
    return [{'label': i, 'value': i} for i in all_options[selected_country]]

@app.callback(
    dash.dependencies.Output('cities-dropdown', 'value'),
    [dash.dependencies.Input('cities-dropdown', 'options')])
def set_cities_value(available_options):
    return available_options[0]['value']

@app.callback(
    dash.dependencies.Output('display-selected-values', 'children'),
    [dash.dependencies.Input('countries-dropdown', 'value'),
     dash.dependencies.Input('cities-dropdown', 'value')])
def set_display_children(selected_country, selected_city):
    return u'{} is a city in {}'.format(
        selected_city, selected_country,
    )

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