Changing a responsive Navbar's content when changing pages with the pages/feature

Visual explanation for the title:

MWE_Gif

I want to change whatever is inside the navbar (there will be dropdown, sliders, radio items etc., in there, like this, but I don’t want to use FastDash like it’s used in this page) when I change pages, with the addiction that the navbar is collapsible and there is some persistence, that is, the navbar will share the same state (opened or closed) when I change pages.

I just copied the code from here to make the bar collapsible and adapted it to each page of the app.

Here is the code:

App.py

import dash
from dash import Dash, html, dcc, callback, Input, Output, State
import dash_mantine_components as dmc



app = Dash(__name__, use_pages=True)
server = app.server


page = list(dash.page_registry.values())


menu1 = dmc.Menu(
    [
        dmc.MenuTarget(dmc.Button('Pages')),
        dmc.MenuDropdown(
            [
                dmc.MenuItem(
                    'Page 1',
                    href=page[0]['path'],
                ),

                dmc.MenuItem(
                    'Page 2',
                    href=page[1]['path'],
                ),
            ]
        )
    ]
)



navwidth = 1


#----------------------------------------------------------------------------------------------------
app.layout = \
    html.Div(
        children=[
            dmc.Grid(
                children=[
                    dmc.Col(
                        html.Div(
                            children=[
                                "Minimal working example"
                            ],
                            style={'fontSize': 30, 'textAlign': 'left'}),
                        span='content', offset=2),

                    dmc.Col(menu1, span='content', offset=0),
            ]),


            html.Hr(),

            html.Div(
                children=[
                    dash.page_container,
                ],
                style={'position':'relative', 'top':5}
            )
        ]
    )





if __name__ == "__main__":
    app.run(debug=True, port=8060)

Pg1.py

import dash
from dash import Dash, html, dcc, Input, Output, State, callback
import dash_mantine_components as dmc



external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

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




navwidth = 1


#----------------------------------------------------------------------------------------------------
layout = dmc.Container(
    children=[
        html.Div(id='nav-div',
                 children=[
                     dmc.Navbar(
                         id='sidebar',
                         fixed=False,
                         hidden=True,
                         width={"base": navwidth},
                         position='right',
                         children=[
                             'Page 1 navbar content \n 111111111'
                         ],
                         style={
                             "overflow": "hidden",
                             "transition": "width 0.3s ease-in-out",
                             "background-color": "#f4f6f9",
                         },
                     ),
                 ]
        ),



        dmc.Container(
            children=[
                dmc.Burger(id='sidebar-button'),

                dmc.Container(
                    'Page 1 content'
                ),
            ],
            size="100%",
            p=0,
            m=0,
            style={"display": "flex", "maxWidth": "100vw", "overflow": "hidden",
                   "flexGrow": "1", "maxHeight": "100%", "flexDirection": "column"},
            id="content-container"
        ),
    ],
    size="100%",
    p=0,
    m=0,
    style={"display": "flex", "maxWidth": "100vw", "overflow": "hidden", "maxHeight": "100vh",
           "position": "absolute", "top": 0, "left": 0, "width": "100vw"},
    id="overall-container"
)



#--------------------------------------------------------------------
@callback(
    Output("sidebar", "width"),
    Input("sidebar-button", "opened"),
    State("sidebar", "width"),
    prevent_initial_call=True,
)
def drawer_demo(opened, width):
    if opened:
        if width["base"] == navwidth:
            return {"base": 200}
        else:
            return {"base": navwidth}
    else:
        if width["base"] == navwidth:
            return {"base": 200}
        else:
            return {"base": navwidth}




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

Page 2 is basically the same, but I’ve changed some component id’s and some content from 1 to 2.

So I made what is shown in the gif by recreating this layout in each page, but I want to create this layout only in the main app and then change the sidebar’s content with dcc.Store, but I don’t know how to use it to pass data to the Dmc.Navbar’s children, I also tried creating an empty Html.Div in the main app, in order to make it the Output of a callback in each page and returning a Navbar to it’s children, something like this:

@callback(
      Output('html-div', 'children')
      Input('Something exclusive to page X, 'Some property')
)

def return_navbar(property):
    
    if property == "Something that ensure I'm in page X":
        navbar = dmc.Navbar(
            id='sidebarX',
            fixed=False,
            hidden=True,
            width={"base": navwidth},
            position='right',
            children=[
                'Page X navbar content \n XXXXXXXXX'
            ],
            style={
                "overflow": "hidden",
                "transition": "width 0.3s ease-in-out",
                "background-color": "#f4f6f9",
            },
        ),
    
    else:
        navbar = ""
        
    return navbar

I’m also avoiding creating the callbacks in the main page, because then I’d have to define all the items I want to go in the Navbar, for each page, in the main app and it’d make it very loaded, I just want to define the elements in each page and pass it to the Div in the main app.

Update:

Semi-functional

I almost succeeded in creating what I wanted, but I still have to change page 2 times before it starts working, it also show some error messages. Here’s the code:

App.py

import dash
from dash import Dash, html, dcc, callback, Input, Output, State
import dash_mantine_components as dmc



app = Dash(__name__, use_pages=True)
server = app.server


page = list(dash.page_registry.values())


menu1 = dmc.Menu(
    [
        dmc.MenuTarget(dmc.Button('Pages')),
        dmc.MenuDropdown(
            [
                dmc.MenuItem(
                    'Page 1',
                    href=page[0]['path'],
                ),

                dmc.MenuItem(
                    'Page 2',
                    href=page[1]['path'],
                ),
            ]
        )
    ]
)



navwidth = 1


#----------------------------------------------------------------------------------------------------
app.layout = \
    dmc.Container(
        children=[
             dmc.Navbar(
                 id='sidebar',
                 fixed=False,
                 hidden=True,
                 width={"base": navwidth},
                 position='right',
                 children=[],
                 style={
                     "overflow": "hidden",
                     "transition": "width 0.3s ease-in-out",
                     "background-color": "#f4f6f9",
                 },
             ),

            dmc.Container(
                children=[
                    dmc.Grid(
                        children=[
                            dmc.Col(
                                dmc.Burger(id='sidebar-button'),
                                span='content',
                            ),

                            dmc.Col(
                                html.Div(
                                    children=[
                                        "Minimal working example"
                                    ],
                                    style={'fontSize': 30, 'textAlign': 'left'}),
                                span='content', offset=2),

                            dmc.Col(menu1, span='content', offset=0),
                        ]),

                    html.Hr(),

                    dmc.Container(
                        children=[
                            dash.page_container,
                        ],
                    )
                ],
                size="100%",
                p=0,
                m=0,
                style={"display": "flex", "maxWidth": "100vw", "overflow": "hidden",
                       "flexGrow": "1", "maxHeight": "100%", "flexDirection": "column"},
                id="content-container"
            ),

            dcc.Location(id='url', refresh=True),
            dcc.Store(id='Nav-content'),
            dcc.Store(id='Nav-content2'),
        ],
        size="100%",
        p=0,
        m=0,
        style={"display": "flex", "maxWidth": "100vw", "overflow": "hidden", "maxHeight": "100vh",
               "position": "absolute", "top": 0, "left": 0, "width": "100vw"},
        id="overall-container"
    )




#--------------------------------------------------------------------
@callback(
    Output("sidebar", "width"),
    Input("sidebar-button", "opened"),
    State("sidebar", "width"),
    prevent_initial_call=True,
)
def drawer_demo(opened, width):
    if width["base"] == navwidth:
        return {"base": 200}
    else:
        return {"base": navwidth}


@callback(
    Output('sidebar', 'children'),
    Input('url', 'pathname'),
    State('Nav-content', 'data'),
    State('Nav-content2', 'data'),
    prevent_initial_call=True
)

def nav_content(url, content, content2):
    if url == '/':
        nav_content = content
    elif url == '/pg2':
        nav_content = content2

    print('Current url:', url)

    return nav_content





if __name__ == "__main__":
    app.run(debug=True, port=8060)

And the pg1.py:

import dash
from dash import Dash, html, dcc, Input, Output, State, callback
import dash_mantine_components as dmc



external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

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





#----------------------------------------------------------------------------------------------------
layout = dmc.Container(
    children=[
        'Page 1 content',
    ],
    id='page1-container'
)


@callback(
    Output('Nav-content','data'),
    Input('url','pathname'),
    prevent_initial_call=True
)

def get_navbar(url):
    if url == '/':
        content = 'Nav content page 1'
        # print('Page 1:', url)

    return content


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

I created a Dcc.Location to work as an Input for the callback both in the main app and the pages and a Dcc.Store for each page, in order to make a callback Output that will return the Navbar content for all pages.

I’m aware that the error message is because both pages trigger the callback at the same time but one of them will not satisfy the if condition inside the function, leaving the variable with no value, but I already tried all possible combinations of prevent_initial_call=True for the callbacks in the main app and the pages, to see if I can make only one callback work at a time, but I still get the same problems shown in the gif, using an else statement for the if function also doesn’t work.

Hey @Galliard I actually would do something like this:

  • one callback in app.py for the content of the navbar
  • pages contain the page content only
  • the dcc.Store() components are not necessary

That’s the concerning callback.

@callback(
    Output('sidebar', 'children'),
    Input('url', 'pathname'),
)
def nav_content(url):

    # if your content is static, you do not necessarily need these functions
    def content_0():
        return 'Navbar content for page 0'

    def content_1():
        return 'Navbar content for page 1'

    # select navbar content depending on url
    content = {
        '/': content_0(),
        '/pg2': content_1()
    }.get(url, 'default content')

    return content

Full example

app.py

import dash
from dash import Dash, html, dcc, callback, Input, Output, State
import dash_mantine_components as dmc

app = Dash(__name__, use_pages=True)
server = app.server

page = list(dash.page_registry.values())

menu1 = dmc.Menu(
    [
        dmc.MenuTarget(dmc.Button('Pages')),
        dmc.MenuDropdown(
            [
                dmc.MenuItem(
                    'Page 1',
                    href=page[0]['path'],

                ),

                dmc.MenuItem(
                    'Page 2',
                    href=page[1]['path'],
                ),
            ]
        )
    ]
)

navwidth = 1

# ----------------------------------------------------------------------------------------------------
app.layout = \
    dmc.Container(
        children=[
            dmc.Navbar(
                id='sidebar',
                fixed=False,
                hidden=True,
                width={"base": navwidth},
                position='right',
                children=[],
                style={
                    "overflow": "hidden",
                    "transition": "width 0.3s ease-in-out",
                    "background-color": "#f4f6f9",
                },
            ),

            dmc.Container(
                children=[
                    dmc.Grid(
                        children=[
                            dmc.Col(
                                dmc.Burger(id='sidebar-button'),
                                span='content',
                            ),

                            dmc.Col(
                                html.Div(
                                    children=[
                                        "Minimal working example"
                                    ],
                                    style={'fontSize': 30, 'textAlign': 'left'}),
                                span='content', offset=2),

                            dmc.Col(menu1, span='content', offset=0),
                        ]),

                    html.Hr(),

                    dmc.Container(
                        children=[
                            dash.page_container,
                        ],
                    )
                ],
                size="100%",
                p=0,
                m=0,
                style={"display": "flex", "maxWidth": "100vw", "overflow": "hidden",
                       "flexGrow": "1", "maxHeight": "100%", "flexDirection": "column"},
                id="content-container"
            ),

            dcc.Location(id='url', refresh=True),
            dcc.Store(id='Nav-content'),
            dcc.Store(id='Nav-content2'),
        ],
        size="100%",
        p=0,
        m=0,
        style={"display": "flex", "maxWidth": "100vw", "overflow": "hidden", "maxHeight": "100vh",
               "position": "absolute", "top": 0, "left": 0, "width": "100vw"},
        id="overall-container"
    )


# --------------------------------------------------------------------
@callback(
    Output("sidebar", "width"),
    Input("sidebar-button", "opened"),
    State("sidebar", "width"),
    prevent_initial_call=True,
)
def drawer_demo(opened, width):
    if width["base"] == navwidth:
        return {"base": 200}
    else:
        return {"base": navwidth}


@callback(
    Output('sidebar', 'children'),
    Input('url', 'pathname'),
)
def nav_content(url):

    # if your content is static, you do not necessarily need these functions
    def content_0():
        return 'Navbar content for page 0'

    def content_1():
        return 'Navbar content for page 1'

    # select navbar content depending on url
    content = {
        '/': content_0(),
        '/pg2': content_1()
    }.get(url, 'default content')

    return content


if __name__ == "__main__":
    app.run(debug=True, port=8060)

pg1.py

import dash
import dash_mantine_components as dmc


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

layout = dmc.Container(
    children=[
        'Page 1 content',
    ],
    id='page1-container'
)

pg2.py

import dash
import dash_mantine_components as dmc


dash.register_page(__name__, name='Page 1', path='/pg2')

layout = dmc.Container(
    children=[
        'Page 2 content',
    ],
    id='page2-container'
)