0

I have made a simple sidebar according to example in https://dash-bootstrap-components.opensource.faculty.ai/examples/simple-sidebar/

Now I need change that navbar content when user changes pages. I would like to make it dynamic & hierarchical.

This means that I should change the sidebar layout when the page changes. That is quite easy when loading normal page content, but sidebar component does not change from page to page.

Therefore I believe the key question is how to refresh the app.layout once the user clicks a new page, and the script has modified (modify_test) in the link_list below, respectively?

Here is the essential part of the sidebar code (!NOT WORKING!):

link_list = [
            dbc.NavLink("Home", href="/", active="exact"),
            dbc.NavLink("Page 1", href="/page-1", active="exact"),
            dbc.NavLink("Page 2", href="/page-2", active="exact"),
            ]

def modify_test ():
    link_list.append(dbc.NavLink("Page 3", href="/page-3", active="exact"))

sidebar = html.Div(
[
    html.H2("Sidebar", className="display-4"),
    html.Hr(),
    html.P(
        "A simple sidebar layout with navigation links", className="lead"
    ),
    dbc.Nav(
        link_list,
        vertical=True,
        pills=True,
    ),
],
style=SIDEBAR_STYLE,
)

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

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


@app.callback(Output("page-content", "children"), [Input("url", "pathname")])
def render_page_content(pathname):
if pathname == "/":
    return html.P("This is the content of the home page!")
elif pathname == "/page-1":
    modify_test ()
    return html.P("This is the content of page 1. Yay!")
elif pathname == "/page-2":
    return html.P("Oh cool, this is page 2!")
# If the user tries to reach a different page, return a 404 message
return dbc.Jumbotron(
    [
        html.H1("404: Not found", className="text-danger"),
        html.Hr(),
        html.P(f"The pathname {pathname} was not recognised..."),
    ]
)
perza
  • 403
  • 7
  • 14
  • 1
    Can you explain in what way you want to change the sidebar layout when the page changes? It seems to me you could handle it by just adding an extra `Output` to your callback that targets your sidebar. – 5eb Nov 12 '21 at 17:02
  • @BasvanderLinden I want to make it hierarchical, so there would be twisties and dropdown lists. And the list contents change too based on the user actions. I am a newbie to Dash and web clients, so I may be missing some obvious alternatives. E.g. I do not know what you mean by adding extra Output; how should that help? – perza Nov 18 '21 at 10:22

1 Answers1

0

Now I need change that navbar content when user changes pages. I would like to make it dynamic & hierarchical.

This means that I should change the sidebar layout when the page changes. That is quite easy when loading normal page content, but sidebar component does not change from page to page.

Therefore I believe the key question is how to refresh the app.layout once the user clicks a new page, and the script has modified (modify_test) in the link_list below, respectively?

I don't know what you want to do exactly, but I don't think it's necessary to refresh the entire layout. Callbacks allow us to target and update specific elements on the page without needing to update the entire page.

Since you already have a callback that runs when the page changes you could add an id to your sidebar and an Output that targets your sidenav by its id and updates some property of the sidenav is some way.

For example could change your callback to something like this

@app.callback(
    Output("sidebar", "style"),
    Output("page-content", "children"),
    Input("url", "pathname"),
)
def render_page_content(pathname):
    if pathname == "/":
        return {"backgroundColor": "blue"}, html.P(
            "This is the content of the home page!"
        )
    elif pathname == "/page-1":
        return {"backgroundColor": "green"}, html.P(
            "This is the content of page 1. Yay!"
        )
    elif pathname == "/page-2":
        return {"backgroundColor": "purple"}, html.P("Oh cool, this is page 2!")
    # If the user tries to reach a different page, return a 404 message
    return {"backgroundColor": "red"}, dbc.Jumbotron(
        [
            html.H1("404: Not found", className="text-danger"),
            html.Hr(),
            html.P(f"The pathname {pathname} was not recognised..."),
        ]
    )

The callback above changes the page content and the style of the sidebar when the page changes.

This is just to give an idea of how you could approach the problem. You probably want to target the children property of the sidebar instead of the style if you want to change the content of the sidebar when the page changes.

Full version previous example

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

app = Dash(__name__)

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

CONTENT_STYLE = {
    "marginLeft": "18rem",
    "marginRight": "2rem",
    "padding": "2rem 1rem",
}

link_list = [
    dbc.NavLink("Home", href="/", active="exact"),
    dbc.NavLink("Page 1", href="/page-1", active="exact"),
    dbc.NavLink("Page 2", href="/page-2", active="exact"),
]

sidebar = html.Div(
    id="sidebar",
    children=[
        html.H2("Sidebar", className="display-4"),
        html.Hr(),
        html.P("A simple sidebar layout with navigation links", className="lead"),
        dbc.Nav(
            link_list,
            vertical=True,
            pills=True,
        ),
    ],
    style=SIDEBAR_STYLE,
)

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

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


@app.callback(
    Output("sidebar", "style"),
    Output("page-content", "children"),
    Input("url", "pathname"),
)
def render_page_content(pathname):
    if pathname == "/":
        return {"backgroundColor": "blue"}, html.P(
            "This is the content of the home page!"
        )
    elif pathname == "/page-1":
        return {"backgroundColor": "green"}, html.P(
            "This is the content of page 1. Yay!"
        )
    elif pathname == "/page-2":
        return {"backgroundColor": "purple"}, html.P("Oh cool, this is page 2!")
    # If the user tries to reach a different page, return a 404 message
    return {"backgroundColor": "red"}, dbc.Jumbotron(
        [
            html.H1("404: Not found", className="text-danger"),
            html.Hr(),
            html.P(f"The pathname {pathname} was not recognised..."),
        ]
    )


if __name__ == "__main__":
    app.run_server()
5eb
  • 14,798
  • 5
  • 21
  • 65
  • It seems you have dropped the modify_test() functionality out of the example. That was the place where the relevant sidebar change took place: adding a new selection item into the sidebar. Without that, the example is unfortunately not useful for me. – perza Nov 24 '21 at 07:39
  • The important part is the idea the example shows not as much what it does exactly. You can take the idea shown in this answer and apply it to your own use case. In your case you could add a `State` that keeps track of your nav items and in your callback return a new list of items: current list of values (state) appended with an option. – 5eb Nov 24 '21 at 08:04
  • Thanks for this idea. One more thing to learn in my study of Dash things. Meanwhile, I circumvented the problem so that app.layout does not have sidebar at all, but each page loads their own, modified version of the sidebar. – perza Nov 25 '21 at 06:35