Multiple dcc.Location items in one application

Is it possible to have multiple dcc.Location elements in one application?

I have a multi-page dash application structured as follows:

app.py
pages
   |-- reviewer.py
   |-- admin.py

My app homepage has two dbc.NavLink items - one for the “reviewer” and one for the “admin”. When the user clicks on “reviewer”, it links to the /reviewer page. The content of the /reviewer page are defined in reviewer.py, which consists of a sidebar with links to new, different pages. When clicked on each of these new links, a different text is to be rendered in an html.Div element defined in the /reviewer page. I have one dcc.Location item for my homepage to link to /render and /admin pages and another dcc.Location item in the /reviewer page to link to the sidebar pages. However, every time I click on a link on a sidebar item on the /reviewer page, the callback function for the homepage’s dcc.Location gets called even though the two dcc.Location items have different ids.

Is there a way to link to the sidebar items on the /reviewer page from a dcc.Location item defined in the /reviewer page? I am trying not to use the homepage’s dcc.Location item to link to the sidebar items to keep the logic of the /reviewer page separate from the homepage’s logic. Below is my code.

Would appreciate any help, thanks!

app.py:

import dash_bootstrap_components as dbc
from dash import html, dcc, callback
from dash_extensions.enrich import DashProxy, Input, Output, ServersideOutputTransform
from pages import reviewer

app = DashProxy(__name__, title='Dash App', external_stylesheets=[dbc.themes.BOOTSTRAP],
                external_scripts=["https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"],
                suppress_callback_exceptions=True, transforms=[ServersideOutputTransform()])

navbar = dbc.NavbarSimple(
    brand="Dash App",
    brand_href="#",
    color="primary",
    dark=True,
    fluid=True,
    sticky='top'
)

login_nav = html.Div([
    navbar,
    dbc.Nav([
        dbc.NavItem(dbc.NavLink("Login as Reviewer", href='/reviewer')),
        dbc.NavItem(dbc.NavLink("Login as Admin", href="/admin")),
    ],
        vertical="md",
        pills=True,
    )
])

app.layout = html.Div([
    dcc.Location(id='main-url', refresh=False),
    html.Div(id='page-content-main'),
])


@callback(
    Output('page-content-main', 'children'),
    Input('main-url', 'pathname')
)
def display_page(pathname):
    if pathname == "/":
        return login_nav
    elif pathname == '/reviewer':
        return reviewer.layout
    elif pathname == '/admin':
        return html.P("Admin Page")
    else:
        return '404'


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

reviwer.py:

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

SIDEBAR_STYLE = {
    "position": "fixed",
    "top": 0,
    "left": 0,
    "bottom": 0,
    "width": "16rem",
    "padding": "2rem 1rem",
    "background-color": "#f8f9fa",
}

CONTENT_STYLE = {
    "margin-left": "18rem",
    "margin-right": "2rem",
    "padding": "2rem 1rem",
}

sidebar = html.Div(
    [
        html.H2("Sidebar", className="display-4"),
        html.Hr(),
        html.P(
            "A simple sidebar layout with navigation links", className="lead"
        ),
        dbc.Nav(
            [
                dbc.NavLink("Page 0", href="/reviewer", active="exact"),
                dbc.NavLink("Page 1", href="/reviewer/page-1", active="exact"),
                dbc.NavLink("Page 2", href="/reviewer/page-2", active="exact"),
            ],
            vertical=True,
            pills=True,
        ),
    ],
    style=SIDEBAR_STYLE,
)

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

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


@callback(Output("page-content", "children"), [Input("url", "pathname")])
def render_page_content(pathname):
    if pathname == "/reviewer":
        return html.P("This is the content of page 0!")
    elif pathname == "/reviewer/page-1":
        return html.P("This is the content of page 1. Yay!")
    elif pathname == "/reviewer/page-2":
        return html.P("Oh cool, this is page 2!")
    return html.Div(
        [
            html.H1("404: Not found", className="text-danger"),
            html.Hr(),
            html.P(f"The pathname {pathname} was not recognised..."),
        ],
        className="p-3 bg-light rounded-3",
    )

Hello @ray26,

dcc.Location is just a way to reference the current location of the window or make changes to it. If you have both registered with the application, then I believe they stay registered.

The easiest way is to just have one dcc.Location and to then utilize ctx to “keep ‘em separated.”

2 Likes

Hi @jinnyzor,

Thanks for the response! I’m still a little confused about how you would do it. I am trying to use ctx but it’s not working. Would it be possible to get a working solution? Thanks a lot!

@ray26,

Are you purposely trying to use index instead of pages?

With pages, you can use things like variable pathnames:

In which case, you could cater different layouts based upon the variable and it gets thrown into the layout.

Hi @jinnyzor,

If I use pages, there does not seem to be a way to replace all of the homepage’s contents with a new page’s contents. A new page’s contents seem to be only added to the homepage’s contents depending on where dash.page_container is defined. I want the homepage to be the login page where you can login as a reviewer/viewer or as an admin. So the homepage should have two links - one for reviewer and one for admin. When the user clicks on “reviewer”, for example, I want everything in the homepage to be replaced with the reviewer page’s contents. The reviewer page should have a left-side navigation menu that has navlinks for different fruits let’s say. Clicking on different fruits should render different contents on the html.Div component right next to the left-side menu.

Would really appreciate your help!

@ray26,

There actually is a way to use pages for this, the navbar needs to become a function however.

When the page’s location is something other than /login or /logout and the nav’s children are blank, then you cater the navbar based upon who logged in. When it is /login or /logout, return an empty [] to the navbar.

I have an app that caters entirely different navs and layouts according to who is logged in. Mind you, I have it sitting behind a couple of flask templates for login and logout.