javascriptpythoncssplotlyplotly-dash

Adding JavaScript to my Plotly Dash app (Python)


I'm building a dashboard using Dash in Python. I have configured all the graphs nicely (it's running on the server here) and the next step is to create a responsive navbar and a footer. Currently looks like this:

enter image description here

And when I shrink the width, it looks like this:

enter image description here

I want to add functionality to this button so it would hide the three links on click. I'm trying to toggle the CSS 'active' attribute using JavaScript with this piece of code:

 var toggleButton = document.getElementsByClassName('toggle-button')[0]
 var navBarLinks = document.getElementsByClassName('navbar-links')[0]

 function toggleFunction() {
     navBarLinks.classList.toggle('active')
 }

 toggleButton.addEventListener('click', toggleFunction)

Basically, when the navbar-links class is active, I want it to be set as display: flex, and when it's not active I want it to be display: none

The HTML elements defined in Python screen are here:

    html.Nav([

    html.Div('Covid-19 global data Dashboard', className='dashboard-title'),

    html.A([html.Span(className='bar'),
            html.Span(className='bar'),
            html.Span(className='bar')],
           href='#', className='toggle-button'),

    html.Div(
        html.Ul([
            html.Li(html.A('Linked-In', href='#')),
            html.Li(html.A('Source Code', href='#')),
            html.Li(html.A('CSV Data', href='#'))
        ]),
        className='navbar-links'),

], className='navbar')

I didn't expect that there would be issues with accessing elements through JavaScript. After doing some research I found out that JavaScript when executes getElementsByClassName function the returned value is null. That is because the function is run before the page is rendered (as far as I understand). It gives me this error:

enter image description here

This project is getting quite big, so I don't know which parts should I include in this post, but I will share the git repository and the preview of the page. Is there an easy solution to it?


Solution

  • Dash callback solution (no Javascript):

    import dash
    import dash_html_components as html
    from dash.dependencies import Output, Input, State
    
    navbar_base_class = "navbar-links"
    
    app = dash.Dash(__name__)
    
    app.layout = html.Nav(
        [
            html.Div("Covid-19 global data Dashboard", className="dashboard-title"),
            html.A(
                id="toggle-button",
                children=[
                    html.Span(className="bar"),
                    html.Span(className="bar"),
                    html.Span(className="bar"),
                ],
                href="#",
                className="toggle-button",
            ),
            html.Div(
                id="navbar-links",
                children=html.Ul(
                    children=[
                        html.Li(html.A("Linked-In", href="#")),
                        html.Li(html.A("Source Code", href="#")),
                        html.Li(html.A("CSV Data", href="#")),
                    ],
                ),
                className=navbar_base_class,
            ),
        ],
        className="navbar",
    )
    
    
    @app.callback(
        Output("navbar-links", "className"),
        Input("toggle-button", "n_clicks"),
        State("navbar-links", "className"),
        prevent_initial_call=True,
    )
    def callback(n_clicks, current_classes):
        if "active" in current_classes:
            return navbar_base_class
        return navbar_base_class + " active"
    
    
    if __name__ == "__main__":
        app.run_server(debug=True)
    

    The idea of the code above is to take the toggle-button click as Input and the current value of navbar-links as State. We can use this state to determine if we should add the active class or remove it. The new className value is returned in the callback.


    Javascript solution:

    window.addEventListener("load", function () {
      var toggleButton = document.getElementsByClassName("toggle-button")[0];
      var navBarLinks = document.getElementsByClassName("navbar-links")[0];
    
      function toggleFunction() {
        navBarLinks.classList.toggle("active");
      }
    
      toggleButton.addEventListener("click", toggleFunction);
    });
    

    The load event is fired when the whole page has loaded, including all dependent resources such as stylesheets and images. This is in contrast to DOMContentLoaded, which is fired as soon as the page DOM has been loaded, without waiting for resources to finish loading.

    https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event

    DOMContentLoaded would be preferable to use, but it only works for me with load.