create bootstrap drop down menu in plotly dash using one function to set it up

Viewed 2735

The logic behind the dash core component dropdown and the dash bootstrap components is a bit different and I would like to have the best of both worlds: the nice style from dbc and the functionality from dcc. However, modifying the css for the dcc to make it look nicer is complicated and I could not find an existing solution. To set up the dbc component requires some set up effort as each element in the drop down has an own id. Also if you want to directly get from the dropdown what is the selected value (if you ask the dropdown what are you actually showing) you cannot do that directly.

I thus wanted to setup a function that sets the drop down and its callback up automatically but I run into the problem that the callback is a nested function and is therefore not available globally. How can I change that? Or is there another way to build it.

What I want in the end is an easy way to set up the dbc dropdown such that it shows the selected value. This is what I have so far (the not working solution):

import dash
import dash_bootstrap_components as dbc
import dash_html_components as html
from dash.dependencies import Input, Output


def selectable_dropdown(item_id="dropdown", 
                        options=['option_1','option_2']):
    # create the items and the ids for each item
    dropdown_items = [dbc.DropdownMenuItem(item, id=item_id+'_'+item) 
                      for item in options]

    # create the dropdown menu
    dropdown = dbc.DropdownMenu(
                                dropdown_items, 
                                label="none", 
                                addon_type="prepend",
                                bs_size="sm",
                                id=item_id
                                )
        
    output = Output(item_id, "label")
    inputs = [Input(item_id+'_'+item, "n_clicks") for item in options]
    
    @app.callback(output,inputs)
    def update_label(*args):
        # get the triggered item
        ctx = dash.callback_context
        triggered_id = ctx.triggered[0]["prop_id"].split(".")[0]
        # get the label for the triggered id or return no selection
        if (np.array([n==None for n in args]).all()) or not ctx.triggered:
           return "no selection"
        else:
           return [label for label in options if item_id+'_'+label == triggered_id]            
    return dropdown       


app = dash.Dash(
    external_stylesheets=[dbc.themes.BOOTSTRAP]
)
app.config['suppress_callback_exceptions'] = True

app.layout = \
    html.Div([selectable_dropdown(item_id="target_select",
                                  options=["option1 ", "option 2", "option3"])])
    
        
if __name__ == "__main__":
    app.run_server(debug=False, host = '0.0.0.0', port = 1234)

thats how it should look like (a working example) but I wnat it in a more generalized way and in the best way just in one function or class:

import dash
import numpy as np
import dash_bootstrap_components as dbc
import dash_html_components as html
from dash.dependencies import Input, Output

options=["option1 ", "option 2", "option3"]
item_id = 'dropdown'
dropdown_items = [dbc.DropdownMenuItem(item, id=item_id+'_'+item) 
                  for item in options]

# create the dropdown menu
dropdown = dbc.DropdownMenu(
                            dropdown_items, 
                            label="none", 
                            addon_type="prepend",
                            bs_size="sm",
                            id=item_id)
    
output = Output(item_id, "label")
inputs = [Input(item_id+'_'+item, "n_clicks") for item in options]

app = dash.Dash(
    external_stylesheets=[dbc.themes.BOOTSTRAP,'./assets/stylesheet.css']
)
app.config['suppress_callback_exceptions'] = True

app.layout = \
    html.Div([dropdown])
   
@app.callback(output,inputs)
def update_label(*args):
    # get the triggered item
    ctx = dash.callback_context
    triggered_id = ctx.triggered[0]["prop_id"].split(".")[0]
    # get the label for the triggered id or return no selection
    if (np.array([n==None for n in args]).all()) or not ctx.triggered:
        return "no selection"
    else:
        return [label for label in options if item_id+'_'+label == triggered_id]
 
        
if __name__ == "__main__":
    app.run_server(debug=False, host = '0.0.0.0', port = 1234)
0 Answers
Related