Callback with navbar

Hi all,

I have this code below. I’ve created 3 navigation links: Inputs, Calculations and About.

Now on the Inputs sheet, I would like to add several dropdowns and input fields for the user. How can I include those input fields and give them ids as I want to use them for calculations later (i.e I create a dropdown and I give it an id of “my-dropdown” to later use for the calculations I want to make in Calculations).

So far this is what I have in the callbacks but if I want to continue my function with an elif pathname == “/inputs” and then I start including my input fields, I will not be able to store their ids to call them later once I want to do the calculations. Can you please advise?

navbar = dbc.NavbarSimple(
    children=[
        dbc.NavItem(dbc.NavLink("INPUTS", href="/inputs", external_link=True, active="exact")),
        dbc.NavItem(dbc.NavLink("CALCULATIONS", href="/calculations", external_link=True, active="exact")),
        dbc.NavItem(dbc.NavLink("ABOUT", href="/about", external_link=True, active="exact")),
        dbc.DropdownMenu(
            children=[
                dbc.DropdownMenuItem("Comments", href="/comments", external_link=True),
                dbc.DropdownMenuItem("Version Updates", href="/updates", external_link=True),

            ],
            nav=True,
            in_navbar=True,
            label="More",
        ),
    ],
    brand="Main Page Title",
    color="#015151",
    dark=True
)

content = html.Div(id="page-content", children=[])

app.layout = html.Div([
    dcc.Location(id="url"),
    navbar,
    content
])


# ---------------------------------------------------------------------------
## APP CALLBACKS ##

# Add info in about section
@app.callback(
    Output("page-content", "children"),
    [Input("url", "pathname")]
)
def render_page_content(pathname):
    if pathname == "/about":
        return [
            html.Br(),
            html.Br(),
            html.P('About', style={'fontWeight': 'bold', 'font-size': 32, 'margin-left': '20px'}),

Hi,

From an implementation POV, the easiest approach is to store the value fields in a dcc.Store component defined on the outermost div in your layout. As you probably noticed, when you navigate to “/calculations”, the inputs won’t be part of the layout anymore and therefore you won’t be able to have them in a callback defined in “/calculations”. The dcc.Store component will be always in the layout, so you can use its props through all the pages.

A few gotchas to be aware of:

  • A callback is triggered when one or more of its inputs are added to the layout, so every time you navigate to “/inputs”, the callback to update dcc.Store is triggered.

  • Because of the point above, it is a good idea to use persistence in the input components, so the current selection will reappear in the components when the user switches back to “/inputs”. Otherwise there is a risk to have the same problems as described in this post. Besides, it is a better pattern not to change the user input on navigation, and perhaps an even better pattern to keep the inputs and results of inputs in the same view, so the user does not have to switch pages to change an input.

  • It is also a good idea to define a validation layout, as explained here.

Hope this helps!

1 Like

Hi,

What if I drop the multi-page and instead put the inputs and calculations on one page so:

navbar = dbc.NavbarSimple(
    children=[
        dbc.NavItem(dbc.NavLink("INPUTS/CALCULATIONS", href="/inputs-and-calcs", external_link=True, active="exact")),
        dbc.NavItem(dbc.NavLink("ABOUT", href="/about", external_link=True, active="exact")),
        dbc.DropdownMenu(
            children=[
                dbc.DropdownMenuItem("Comments", href="/comments", external_link=True),
                dbc.DropdownMenuItem("Version Updates", href="/updates", external_link=True),

            ],
            nav=True,
            in_navbar=True,
            label="More",
        ),
    ],
    brand="Main Page Title",
    color="#015151",
    dark=True
)

content = html.Div(id="page-content", children=[])

app.layout = html.Div([
    dcc.Location(id="url"),
    navbar,
    content
])


# ---------------------------------------------------------------------------
## APP CALLBACKS ##

# Add info in about section
@app.callback(
    Output("page-content", "children"),
    [Input("url", "pathname")]
)
def render_page_content(pathname):
    if pathname == "/about":
        return [
            html.P('About', style={'fontWeight': 'bold', 'font-size': 32, 'margin-left': '20px'}),

So now if I keep building on the callback:

@app.callback(
    Output("page-content", "children"),
    [Input("url", "pathname")]
)
def render_page_content(pathname):
    if pathname == "/about":
        return [
            html.P('About', style={'fontWeight': 'bold', 'font-size': 32, 'margin-left': '20px'}),

    elif pathname == "/inputs-and-calcs":
        return []

I reach this elif statement. There I want to add the input fields (dropdowns) on the left and then the calculations should appear on the right. If I include the dropdowns in the last return statement and give them an id, I will not be able to use that id to use it in another callback because its not in the initial layout. Will the dcc.Store help in this case too?

In this case dcc.Store is not needed, even if the components are not in the initial layout.

You would still need to add a validation layout or set suppress_callback_exceptions=True in your app in order to remove the warning that a callback has inputs not in the layout, but the callback itself (eg. to perform the calculations) will be triggered just when “/inputs-and-calcs” is open.

One more tip: if you have multiple user inputs and your calculations are not super quick, it is better to add a submit button and use it as sole Input in the callback (passing the user inputs as State instead). This is especially true for components like text input and sliders.

1 Like

Sorry for all the questions but I am trying to implement your solution to see if it will work and I am running into issues where the dashboard does not load (it just freezes).

Updated code:

# Initialize app
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.LUX], meta_tags=[{'name': 'viewport',
                                                                             'content': 'width=device-width, initial-scale=1.0'}], suppress_callback_exceptions=True)
navbar = dbc.NavbarSimple(
    children=[
        dbc.NavItem(dbc.NavLink("INPUTS & CALCULATIONS", href="/inputs-and-calculations", external_link=True, active="exact")),
        dbc.NavItem(dbc.NavLink("ABOUT", href="/about", external_link=True, active="exact")),
        dbc.DropdownMenu(
            children=[
                dbc.DropdownMenuItem("Comments", href="/comments", external_link=True),
                dbc.DropdownMenuItem("Version Updates", href="/updates", external_link=True),

            ],
            nav=True,
            in_navbar=True,
            label="More",
        ),
    ],
    brand="Main Page",
    color="#015151",
    dark=True,
    className='mb-4'
)
# Add info in about section
@app.callback(
    Output("page-content", "children"),
    [Input("url", "pathname")]
)
def render_page_content(pathname):
    if pathname == "/about":
        return [
            html.P('About', style={'fontWeight': 'bold', 'font-size': 32, 'margin-left': '20px'}),
        ]

    elif pathname == "/inputs-and-calculations":
        return [
            dbc.Row([
                dbc.Col([
                    dcc.Dropdown(
                        id='demo-dropdown',
                        options=[
                            {'label': 'New York City', 'value': 'NYC'},
                            {'label': 'Montreal', 'value': 'MTL'},
                            {'label': 'San Francisco', 'value': 'SF'}
                        ],
                        value='NYC',
                        clearable=False,
                        style={'margin-left': '10px'}
                    )
                ], width=6),

                dbc.Col([
                    dcc.Graph(id="graph")
                ], width=6)
            ])
        ]


@app.callback(
    Output("graph", "figure"),
    [Input("demo-dropdown", "value")]
)
def update_chart(chosen_city):
    df = pd.DataFrame(dict(
        x=[1, 3, 2, 4],
        y=[1, 2, 3, 4]))

    if 'New York City' in chosen_city:
        fig = px.line(df, x="x", y="y", title="Unsorted Input")
        return fig

Can you please tell me what I am doing wrong?

Thanks a lot for your help! I really appreciate it !!

After a quick glance, I think your conditional will never be satisfied as “New York City” is a label, not a value.

That said, this should not make your app freeze… Two suggestions:

  • Is it freezing when you open “/” or a specific path? Note that if you just start your app and open the browser, this will direct you to pathname="/" and you don’t have a route for that.

  • Run in debug=True and see if an error pops up.

As a side note, there are tons of good work to add a better support for multi-page apps and you might benefit from it.
Please take a look on this post.

1 Like

I am opening it with this code:

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

True for New York City being the label. I have just fixed that to take the value instead of the label. But still with that adjustment the app is freezing when I go to Inputs & Calculations page

Am I doing something wrong in running the app?

Oh sorry, I completely missed that your callback has a return inside the conditional and no return outside it.

Can you try the following instead?

@app.callback(
    Output("graph", "figure"),
    [Input("demo-dropdown", "value")]
)
def update_chart(chosen_city):
    df = pd.DataFrame(dict(
        x=[1, 3, 2, 4],
        y=[1, 2, 3, 4]))

    if 'NYC' in chosen_city:
        fig = px.line(df, x="x", y="y", title="Unsorted Input")
        return fig
    else: 
        raise dash.exceptions.PreventUpdate # or return dash.no_update

This should show an empty graph if “NYC” is not selected and it won’t update it after that, so not entirely the best approach.

Other than that, I can’t see anything wrong, not even what I mentioned about “/” (your index should be a blank page).

Works like a charm!! Thanks a lot for the help and also for sending me the link to the new way of creating multi-page apps!! it’s super helpful!

1 Like