Issues with multi-pages app navigation menu + sidebars/tabs in each page

Hello,

I am trying to create a multipage app with two countries in a navigation bar at the top, and for each country two regions (east and west, as a sidebar), and for each region two cities (as tabs).

All my app code can be found on the github link at the end of this post.

In the reproductible example below, I have the following countries/regions/cities:

At the root/landing of the app, the content of the USA/East/New York tab should be displayed.
The navigation bar with the countries should always be visible.

The app root/landing page should ultimately look like this (with the USA/East Coast/ New York tab showing that they are selected):

When user try to access the app root, they should automatically be redirected to the page above, which url should read: http://127.0.0.1:8050/usa/east

If they click on Germany, they should navigate to http://127.0.0.1:8050/deu/east , which is displayed below:

I have organised my app the following way:

  • app.py
  • styles_info.py
  • init.py
  • pages
    • USA
      |-- usa_index.py
      |-- usa_east_ny_tab.py
      |-- usa_east_philly_tab.py
      |-- usa_west_la_tab.py
      |-- usa_west_sa_tab.py

    • DEU
      |-- deu_index.py
      |-- deu_east_berlin_tab.py
      |-- deu_east_leipzig_tab.py
      |-- deu_west_koln_tab.py
      |-- deu_west_frankfurt_tab.py

The .py files ending with “_tab.py” in their names contain the plotly objects and related dash components that get displayed on each page. I’m just using random dash/plotly objects from examples on the web.

The styles_info.py just contain formatting data for the sidebar menu.

There is a lot not working on that app but the firs things I would like to fix are:

Here is my main app.py script generating the pages:

import dash
from dash import Dash, html
import dash_bootstrap_components as dbc

app = Dash(__name__, use_pages=True, external_stylesheets=[dbc.themes.BOOTSTRAP])

app.layout = html.Div([
                        html.H1('World App'),

                        dbc.Navbar(
                            [
                                dbc.Button(f"{page['name']}",
                                                       href=page["relative_path"],
                                                       color="primary",
                                                       className="me-1")
                                for page in dash.page_registry.values()
                            ],
                        ),

                        dash.page_container,

                        ])


if __name__ == '__main__':

    app.run_server()

Here is the script for the page that should be the landing page when navigating to the app root (http://127.0.0.1:8050), located in usa_index.py :

import dash
import dash_bootstrap_components as dbc
from dash import Input, Output, dcc, html, callback

from pages.USA.usa_east_ny_tab import get_usa_east_ny_content
from pages.USA.usa_east_philly_tab import get_usa_east_philly_content
from pages.USA.usa_west_la_tab import get_usa_west_la_content
from pages.USA.usa_west_sa_tab import get_usa_west_sa_content

from styles_info import get_styles_info

dash.register_page(__name__, name='USA', path = '/usa')

SIDEBAR_STYLE, CONTENT_STYLE = get_styles_info()

sidebar = html.Div(
    [
        html.H2("USA Cities", className="display-8"),
        html.Hr(),
        html.P(
            "Information on USA cities", className="lead"
        ),
        dbc.Nav(
            [
                dbc.NavLink("East Coast", href=f"usa/east/ny", active="exact"),
                dbc.NavLink("West Coast", href=f"usa/west/la", active="exact"),
            ],
            vertical=True,
            pills=True,
        ),
    ],
    style=SIDEBAR_STYLE,
)

usa_east = html.Div([
                            dbc.Tabs(
                                [
                                    dbc.Tab(label="NEW YORK", tab_id="usa_east_ny_id"),
                                    dbc.Tab(label="PHILADELPHIA", tab_id="usa_east_philly_id"),
                                ],
                                id="tabs",
                                active_tab="usa_east_ny_id",
                            ),
                            html.Div(id="usa_tab-content", className="p-4"),
                            ])

usa_west = html.Div([
                            dbc.Tabs(
                                [
                                    dbc.Tab(label="LOS ANGELES", tab_id="usa_west_la_id"),
                                    dbc.Tab(label="SAN FRANCISCO", tab_id="usa_west_sa_id"),
                                ],
                                id="tabs",
                                active_tab="usa_west_la_id",
                            ),
                            html.Div(id="usa_tab-content", className="p-4"),
                            ])

content = html.Div(id="usa_page-content", style=CONTENT_STYLE)

layout = html.Div([dcc.Location(id="url_usa"), sidebar, content])

usa_east_ny_content = get_usa_east_ny_content()
usa_east_philly_content = get_usa_east_philly_content()

usa_west_la_content = get_usa_west_la_content()
usa_west_sa_content = get_usa_west_sa_content()

@callback(
          Output("usa_page-content", "children"),
          [Input("url_usa", "pathname")])

def render_page_content(pathname):

    if pathname == f"/" or pathname == f"/usa" or pathname == f"/usa/east" or pathname == f"/usa/east/ny":
        return usa_east
    elif pathname == f"/usa/west" or pathname == f"/usa/west/la":
        return usa_west


@callback(
          Output("usa_tab-content", "children"),
          [Input("tabs", "active_tab")],
          )

def render_tab_content(active_tab):

    if active_tab:
        if active_tab == "usa_east_ny_id":
            return usa_east_ny_content
        elif active_tab == "usa_east_philly_id":
            return usa_east_philly_content
        elif active_tab == "usa_west_la_id":
            return usa_west_la_content
        elif active_tab == "usa_west_sa_id":
            return usa_west_sa_content

Here is the code of the default tab that is open when on that page (returned in the page by calling the function get_usa_east_ny_content and imported into the usa_index.py page from the file usa_east_ny_tab.py :

def get_usa_east_ny_content():
    
    from dash import dcc, html, Input, Output, callback
    import plotly.express as px
    import pandas as pd

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

    layout = html.Div([
                        dcc.Graph(id='usa_east_ny_graph'),
                        dcc.Slider(
                                    df['year'].min(),
                                    df['year'].max(),
                                    step=None,
                                    value=df['year'].min(),
                                    marks={str(year): str(year) for year in df['year'].unique()},
                                    id='year-slider-ny'
                                    )
                        ])

    @callback(
                Output('usa_east_ny_graph', 'figure'),
                Input('year-slider-ny', 'value')
             )

    def update_figure(selected_year):

        filtered_df = df[df.year == selected_year]

        fig = px.scatter(filtered_df, x="gdpPercap", y="lifeExp",
                         size="pop", color="continent", hover_name="country",
                         log_x=True, size_max=55)

        fig.update_layout(transition_duration=500)

        return fig

    return layout
  • how to set the urls properly so clicking on DEU/East returns the content of the BERLIN tab on this url: http://127.0.0.1:8050/DEU/east/berlin (and the same logic for all other links in the sidebar).

The page for DEU is defined in deu_index.py like this:

import dash
import dash_bootstrap_components as dbc
from dash import Input, Output, dcc, html, callback

from pages.DEU.deu_east_berlin_tab import get_deu_east_berlin_content
from pages.DEU.deu_east_leipzig_tab import get_deu_east_leipzig_content
from pages.DEU.deu_west_koln_tab import get_deu_west_koln_content
from pages.DEU.deu_west_frankfurt_tab import get_deu_west_frankfurt_content

from styles_info import get_styles_info

dash.register_page(__name__, name='DEU', path = '/DEU')

SIDEBAR_STYLE, CONTENT_STYLE = get_styles_info()

sidebar = html.Div(
                    [
                        html.H2("GERMANY Cities", className="display-8"),
                        html.Hr(),
                        html.P("Information on GERMANY cities", className="lead"),
                        dbc.Nav(
                                [dbc.NavLink("East", href=f"/DEU/east/berlin", active="exact"),
                                 dbc.NavLink("West", href=f"/DEU/west/koln", active="exact"),
                                ],
                                vertical=True,
                                pills=True,
                                 ),
                    ],
    style=SIDEBAR_STYLE,
)

deu_east = html.Div([
                    dbc.Tabs(
                                [
                                dbc.Tab(label="BERLIN", tab_id="deu_east_berlin_id"),
                                dbc.Tab(label="LEIPZIG", tab_id="deu_east_leipzig_id"),
                                ],
                                id="tabs",
                                active_tab="deu_east_berlin_id",
                             ),
                    html.Div(id="deu_tab-content", className="p-4"),
                    ])

deu_west = html.Div([
                            dbc.Tabs(
                                    [
                                    dbc.Tab(label="KOLN", tab_id="deu_west_koln_id"),
                                    dbc.Tab(label="FRANKFURT", tab_id="deu_west_frankfurt_id"),
                                    ],
                                    id="tabs",
                                    active_tab="deu_west_koln_id",
                                     ),
                            html.Div(id="deu_tab-content", className="p-4"),
                    ])

content = html.Div(id="deu_page_content", style=CONTENT_STYLE)

layout = html.Div([dcc.Location(id="deu_url"), sidebar, content])

deu_east_berlin_content = get_deu_east_berlin_content()
deu_east_leipzig_content = get_deu_east_leipzig_content()

deu_west_koln_content = get_deu_west_koln_content()
deu_west_frankfurt_content = get_deu_west_frankfurt_content()

@callback(
          Output("deu_page_content", "children"),
          [Input("deu_url", "pathname")])

def render_page_content(pathname):
    if pathname == f"/DEU" or pathname == f"/DEU/east" or pathname == f"/DEU/east/berlin":
        return deu_east
    elif pathname == f"/DEU/west" or pathname == f"/DEU/west/koln":
        return deu_west


@callback(
    Output("deu_tab-content", "children"),
    [Input("tabs", "active_tab")])

def render_tab_content(active_tab):

    if active_tab:
        if active_tab == "deu_east_berlin_id":
            return deu_east_berlin_content
        elif active_tab == "deu_east_leipzig_id":
            return deu_east_leipzig_content
        elif active_tab == "deu_west_koln_id":
            return deu_west_koln_content
        elif active_tab == "deu_west_frankfurt_id":
            return deu_west_frankfurt_content
  • how to get the main Navbar to stay at the top of the page when looking at individual sidebar page / tabs? Right now the buttons in the Navbar disappear when a tab gets displayed:

All the .py files and folders for my app above can be found here: