Automatically collapse `NavBar` whenever one of the `NavLink`s is pressed

In a multi-page Dash app I created a Navbar dynamically based on the pages that the app has

def navbar():
    return dbc.Navbar(
        dbc.Container(
            [
                dbc.NavbarToggler(id='navbar-toggler', n_clicks=0),
                dbc.Collapse(
                    dbc.Nav(
                        [
                            dbc.NavItem(
                                dbc.NavLink(
                                    page['title'],
                                    id=page["relative_path"].split("/")[-1],
                                    href=page["relative_path"]
                                )
                            ) for page in dash.page_registry.values()
                        ],
                    ),
                    id='navbar-collapse',
                    navbar=True
                ),
            ]
        ),
    )

The Navbar automatically collapses on small screens so I added the following callback

@callback(
    Output('navbar-collapse', 'is_open'),
    Input('navbar-toggler', 'n_clicks'),
    State('navbar-collapse', 'is_open'),
)
def toggle_navbar_collapse(n, is_open):
    if n:
        return not is_open
    return is_open

Now, in order to save one click on small screens, I’d like to collapse the Navbar whenever one of the Navlink is pressed. I thought of using pattern matching callbacks, however I’m not sure how to adapt one of the examples in that page. I tried to do something like this

@callback(
    Output('navbar-collapse', 'is_open', allow_duplicate=True),
    Input({'type': 'navbar-link', 'index': ALL}, 'n_clicks'),
    State('navbar-collapse', 'is_open'),
    prevent_initial_call=True
)
def toggle_navbar_collapse_from_navlink(navlink_clicks, is_open):
    if is_open:
        return False

but this is never triggered.
I just want to trigger this callback every time any of the NavLink is pressed without having to write an individual callback per NavLink, obviously.

Did you actually use this id in your code?

Hello @guidocioni,

Assuming you are using pages, why not just listen to changes on the _pages_location instead?

@callback(
    Output('navbar-collapse', 'is_open', allow_duplicate=True),
    Input('_pages_location', 'href'),
    prevent_initial_call=True
)
def pageNavigate(h):
    if h:
        return False
    return no_update

No, and that’s exactly the point.
The examples in the doc only have cases where these components are created in a callback, so their ids are dynamically set using n_clicks. I’m not sure how I have to se the ids in my case where I create the NavLink inside a function without callback.
Sorry for the stupid question, I’m just struggling to understand the concept of this patter matching in the Input. I just want to trigger this callback whenever one of the NavLink attributes n_clicks changes, doesn’t matter which one.

Yeah, I thought about it, and it works pretty well.
However I wanted to handle as well the case when the user clicks on a NavLink that corresponds to the same active page. In this case this callback would not be triggered.
That’s why I thought of using the n_clicks attribute of the NavLinks items.

To utilize pattern-matching, your code needs to be setup as such:

id={'type': 'navbar-link', 'index': page["relative_path"].split("/")[-1]}

Then I think this would start working.

You could always let the user know that they are on the page by using an active class or so, otherwise they might wait for something and not realize that they are on that page already. XD

In fact, I believe that dbc already has this built in natively.

https://dash-bootstrap-components.opensource.faculty.ai/docs/components/nav/

Yes, it is!
Thank you so much.
I think I understand now that, in the case of pattern matching, the id becomes a dict and has additional attributes attached to it.

1 Like

Hey @jinnyzor , do you have any idea how can I prevent this collapse action if I’m not on mobile?

The problem right now is that on PC the collapse is triggered anyway but then the navbar automatically expands because (I guess) it recognizes the larger screen so reverts to the default.

Not a big deal…but would be better to prevent the action from happening if the viewport is large enough

If you make these all clientside callbacks, then you can query the window viewport width, and if big enough, then you can return no_update

1 Like

Good idea,
the following clientside callback seems to handle everything pretty well ( I removed the previous function).

clientside_callback(
    """
    function toggleCollapse(n_clicks) {
        // Check the viewport width
        var viewportWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;

        // Set a threshold for the viewport width when collapse should not happen
        var threshold = 768;  // Adjust this threshold as needed

        // Conditionally toggle the collapse based on viewport size
        if (viewportWidth <= threshold) {
            return false;
        } else {
            return window.dash_clientside.no_update;
        }
    }
    """,
    Output('navbar-collapse', 'is_open', allow_duplicate=True),
    [Input({'type': 'navbar-link', 'index': ALL}, 'n_clicks')],
    prevent_initial_call=True
)
3 Likes