最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Adding JavaScript to my Plotly Dash app (Python) - Stack Overflow

programmeradmin5浏览0评论

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:

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

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:

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?

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:

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

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:

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?

Share Improve this question edited Jul 11, 2021 at 15:47 Sebastian Meckovski asked Jul 11, 2021 at 15:36 Sebastian MeckovskiSebastian Meckovski 2781 gold badge7 silver badges24 bronze badges 7
  • 1 I think you need to create a function that initializes on document or window load time. The JS has probably already run before element toggle-button has been loaded, hence 'undefined error'... – Rene van der Lende Commented Jul 11, 2021 at 16:29
  • Does this answer your question? How to make JavaScript execute after page load? – bas Commented Jul 11, 2021 at 16:54
  • I think it does, but I don't know how to implement that into my JS script due to my JS knowledge limitations – Sebastian Meckovski Commented Jul 11, 2021 at 17:19
  • You can add an event listener for the load event. For an example see the second example of the second answer of the linked post (link). You can put the code in your question inside the handler function for the load event and that will probably work. – bas Commented Jul 11, 2021 at 17:24
  • I don't think you need javascript for this btw. You could also probably do it with a callback in Dash. – bas Commented Jul 11, 2021 at 17:27
 |  Show 2 more ments

4 Answers 4

Reset to default 3

You can defer the execution of JavaScript code until after React has loaded via the DeferScript ponent from dash-extensions. Here is a small example,

import dash
import dash_html_ponents as html
from html import unescape
from dash_extensions import DeferScript


mxgraph = r'{"highlight":"#0000ff","nav":true,"resize":true,"toolbar":"zoom layers lightbox","edit":"_blank","xml":"<mxfile host=\"app.diagrams\" modified=\"2021-06-07T06:06:13.695Z\" agent=\"5.0 (Windows)\" etag=\"4lPJKNab0_B4ArwMh0-7\" version=\"14.7.6\"><diagram id=\"YgMnHLNxFGq_Sfquzsd6\" name=\"Page-1\">jZJNT4QwEIZ/DUcToOriVVw1JruJcjDxYho60iaFIaUs4K+3yJSPbDbZSzN95qPTdyZgadm/GF7LAwrQQRyKPmBPQRzvktidIxgmwB4IFEaJCUULyNQvEAyJtkpAswm0iNqqegtzrCrI7YZxY7Dbhv2g3r5a8wLOQJZzfU4/lbByoslduPBXUIX0L0cheUrugwk0kgvsVojtA5YaRDtZZZ+CHrXzukx5zxe8c2MGKntNgknk8bs8fsj3+KtuDhxP+HZDVU5ct/RhatYOXgGDbSVgLBIG7LGTykJW83z0dm7kjklbaneLnEnlwFjoL/YZzb93WwNYgjWDC6EEdkuC0cZEO7p3i/6RF1WutL8nxmnkxVx6UcUZJIy/LgP49622mO3/AA==</diagram></mxfile>"}'
app = dash.Dash(__name__)
app.layout = html.Div([
    html.Div(className='mxgraph', style={"maxWidth": "100%"}, **{'data-mxgraph': unescape(mxgraph)}),
    DeferScript(src='https://viewer.diagrams/js/viewer-static.min.js')
])

if __name__ == '__main__':
    app.run_server()

Dash callback solution (no Javascript):

import dash
import dash_html_ponents 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/en-US/docs/Web/API/Window/load_event

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

If you need a pure JS solution you need to use MutationObserver. I've wrote a little helper function we are currently using that did the trick. Another suggestion would be to change the mutation to an element on screen then fire an event to handle the rest

/**
 *
 * @param {string} id
 * @param {*} event
 * @param {(this: HTMLElement, ev: any) => any} callback
 * @param {boolean | AddEventListenerOptions} options
 */
function attachEventToDash(id, event, callback, options) {
    debugger;
    var observer = new MutationObserver(function (_mutations, obs) {
        var ele = document.getElementById(id);
        if (ele) {
            debugger;
            ele.addEventListener(event, callback, options)
            obs.disconnect();
        }
    });
    window.addEventListener('DOMContentLoaded', function () {
        observer.observe(document, {
            childList: true,
            subtree: true
        });
    })
}

Pure Python Implementation:

This is a little late but hopefully it will help someone else with the same issue.

In theory this callback process can be used with any element that's predefined in a plolty dash application and removes the need for js.

Main.py Header Structure:

    html.Header(
        html.Div(
            className = 'nav-container',
            id = 'nav-container',
            children = [
                html.Div(
                    children = [
                        html.H2('Brand'),
                        html.Div(
                            className = 'nav-toggle',
                            id = 'nav-toggle',
                            children = [
                                html.Div(className='top-line'),
                                html.Div(className='middle-line'),
                                html.Div(className='bottom-line'),
                            ]
                        )
                    ]
                ),
                html.Nav(
                    html.Ul(
                        children = [
                            html.Li(html.A('Link', href='/')),
                            html.Li(html.A('Link', href='/')),
                            html.Li(html.A('Link', href='/')),
                            html.Li(html.A('Link', href='/')),
                        ]
                    )
                )
            ]
        )
    ),
  • The top/middle/bottom lines are for the css styling that i use to animate the interaction with the menu. (Hamburger)
  • In CSS, I set the inital height of the menu to 50px.
  • I have additional CSS styling that's applied to smaller screen sizes to format the menu as shown in the photos below.

Callback Implementation:

@callback(
    Output('nav-container', 'style'),
    Input('nav-toggle', 'n_clicks'),
)
def toggle_nav(n_clicks):
    if n_clicks is None:
        return no_update
    else:
        if n_clicks % 2 == 1:
            return {
                'height':'fit-content',
            }
        else:
            return {
                'height':'50px',
                'overflow':'hidden',
            }
  • 'n_clicks' is none on launch.
  • Mod 2 maintains state based on the number of clicks for the menu div. (So long as the browser isn't refreshed.)

This is the menu when the application launches. Menu Closed:

Menu Closed

This is the menu when the button is toggled. Menu Open:

Menu Open

发布评论

评论列表(0)

  1. 暂无评论