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
4 Answers
Reset to default 3You 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