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?
You can defer the execution of JavaScript code until after React has loaded via the DeferScript component from dash-extensions. Here is a small example,
import dash
import dash_html_components 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.net\" 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.net/js/viewer-static.min.js')
])
if __name__ == '__main__':
app.run_server()
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.
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
});
})
}
I am using this js library (bootstrap-stars-rating) to provide star ratings using jquery in my Rails 5 application
It works well except that if I am calling it inside a partial that is renerdered by Ajax helper, it will not initialize the javascript code necessary to render the stars
I tried to solve this by making a globally accessible function in my webpacker init file for that library like this:
import "bootstrap-star-rating";
import "bootstrap-star-rating/css/star-rating.css";
import "bootstrap-star-rating/js/star-rating.js";
import "bootstrap-star-rating/themes/krajee-fas/theme.min.css";
const rating_options = {
size: 'xs',
showCaption: false,
step: 0.5,
filledStar: '<i class="fas fa-star"></i>',
emptyStar: '<i class="far fa-star"></i>',
theme: 'krajee-fas',
hoverOnClear: false
}
// initialize the star rating across the application
const initStarRating = () => {
document.addEventListener("turbolinks:load", () => {
console.log('hey I was run');
$(".bootstrap-rating").rating(rating_options);
}, {passive: true});
}
// initialize specific star rating in a specific form for ajax
const initStarRatingForObj = (obj) => {
{
console.log(obj)
console.log(obj.find(".bootstrap-rating"))
obj.find(".bootstrap-rating").rating(rating_options);
}
}
// binding the function so I can access it globally
window.initStarRatingForObj = initStarRatingForObj
export { initStarRating }
The problem is that if I call this function above in the js.erb file that renders the partial, passing the correct object to the function initStarRatingForObj, it will break & give error that it can't find the rating() function ((that one called in the initStarRating))
This is very weird as the rating() function is already available & working in that file, so how does it work in initStarRating() but not in the initStarRatingForObj() ?
I have been looking into this for hours but it doesn't work, I hope someone can point out what the issue is.
Note:
The same partial is working well in the same page if it is not rendered by Ajax response
I am pretty new to SAPUI5 and have a little problem with my App. I set up a Master-Detail Application which shows a list of Customers in the Master View. My goal is to filter this list to any property of my oData Service (I am using the Northwind Webservice).
Here you can see a snippet with the list of my view (MasterView.xml):
<List
id="list"
items="{
path: '/Customers',
sorter: {
path: 'CompanyName',
descending: false
},
groupHeaderFactory: '.createGroupHeader'
}"
busyIndicatorDelay="{masterView>/delay}"
noDataText="{masterView>/noDataText}"
mode="{= ${device>/system/phone} ? 'None' : 'SingleSelectMaster'}"
growing="true"
growingScrollToLoad="true"
updateFinished="onUpdateFinished"
selectionChange="onSelectionChange">
<infoToolbar>
<Toolbar
active="true"
id="filterBar"
visible="{masterView>/isFilterBarVisible}"
press="onOpenViewSettings">
<Title
id="filterBarLabel"
text="{masterView>/filterBarLabel}" />
</Toolbar>
</infoToolbar>
<items>
<ObjectListItem
type="{= ${device>/system/phone} ? 'Active' : 'Inactive'}"
press="onSelectionChange"
title="{CompanyName}"
numberUnit="{CustomerID}">
</ObjectListItem>
</items>
</List>
And here is what I have done in my controller (Master.controller.js):
onInit : function () {
// Control state model
var oList = this.byId("list"),
oViewModel = this._createViewModel(),
// Put down master list's original value for busy indicator delay,
// so it can be restored later on. Busy handling on the master list is
// taken care of by the master list itself.
iOriginalBusyDelay = oList.getBusyIndicatorDelay();
// Tryout Filter
var equals = FilterOperator.EQ;
var aFilterFoo = [];
aFilterFoo.push(new Filter("Country", equals, "Germany"));
var oBinding = oList.getBinding("items");
oBinding.filter(aFilterFoo);
// End tryout Filter
this._oList = oList;
// keeps the filter and search state
this._oListFilterState = {
aFilter : [],
aSearch : []
};
this.setModel(oViewModel, "masterView");
// Make sure, busy indication is showing immediately so there is no
// break after the busy indication for loading the view's meta data is
// ended (see promise 'oWhenMetadataIsLoaded' in AppController)
oList.attachEventOnce("updateFinished", function(){
// Restore original busy indicator delay for the list
oViewModel.setProperty("/delay", iOriginalBusyDelay);
});
this.getView().addEventDelegate({
onBeforeFirstShow: function () {
this.getOwnerComponent().oListSelector.setBoundMasterList(oList);
}.bind(this)
});
this.getRouter().getRoute("master").attachPatternMatched(this._onMasterMatched, this);
this.getRouter().attachBypassed(this.onBypassed, this);
},
This was all set up automatically by SAP Web IDE. I only changed the code inbetween the comments //Tryout Filter and //End Tryout
When i want to run my application, debugger says: "Cannot read property 'filter' of undefined" because oBinding is undefined.
I hope any of you can help me.
Thanks a lot and best regards
Solved this problem by getting into the sap lifecycle methods. onBeforeRendering() was the function I used in my Master.Controller.js
Is there any analogues functions $apply or $digest in Aurelia ?
How immediately call changes bindingValue?
My case:
I have tree(each node contain list of item, screen 1). I have parent component(names: tree), and node component(names:node).
Each node have a toggle button. When i'm toggle my items parent component should know how changes content height.
toggleNode(event) {
for (var i = 0, length = this.current.children.length; i < length; i++) {
this.current.children[i].visible = !this.current.children[i].visible;
}
//some code
this.evAggregator.publish("toggle-agents", event);
}
View:
<ol show.bind="current.visible">//Some markup</ol>
My parent component catch this event and check content size:
#autoinject
export class Agents {
constructor(private evAggregator: EventAggregator) {
this.toggleAgentsSubscriber = this.evAggregator.subscribe("toggle- agents", (e) => {
//some code for recalculate content height
});
}
}
Now code execute that:
1) this.current.children[i].visible = false(when node collapse)
2) Fire my event "toggle-agents"
3) Subscriber catch my event (recalculate height)
4) In depth at Aurelia ObserverLocator update my (visible property) in DOM and height changes.
I need:
1) this.current.children[i].visible = false(when node collapse)
2) In depth at Aurelia ObserverLocator update my (visible property) in DOM and height changes.
3) Fire my custom event.
4) Subscriber catch my event and recalculate height when height content actually changes.
In Aurelia, changes are immediately applied for the most part. Aurelia only uses dirty checking for computed properties (properties with a getter function). If you wanted to manually invoke the dirty-checking, you could do something like this:
import {DirtyChecker} from 'aurelia-binding';
import {inject} from 'aurelia-dependency-injection';
#inject(DirtyChecker)
export class Foo {
constructor(dirtyChecker) {
dirtyChecker.check(); // force an application-wide dirty check
}
}
In practice, this sort of thing is never needed in an Aurelia app. If you have specific bindings that you want to force to update you can use the signal binding behavior:
<template>
<label>${foo & signal:'my signal name'}</label>
</template>
import {BindingSignaler} from 'aurelia-templating-resources';
import {inject} from 'aurelia-dependency-injection';
#inject(BindingSignaler)
export class Foo {
constructor(signaler) {
signaler.signal('my signal name'); // evaluate any bindings with 'my signal name'
}
}
Try to stay away from these two techniques if you can. If you're using this for something other than time-based bindings (binding to something that uses Date.now() or new Date()) or internationalization locale-changed events... you might not be doing it the "Aurelia way".
I try to build a simple form-like UI where I want to dynamically add rows of multiple TextFields. I built a custom component with XML and JS, because there is some interaction in these TextFields (start, end, duration).
I am a starter in NativeScript, but I got my databinding and event-structure going. I was able to add my custom component via ui/builder. Where I am totally stuck is getting "options/arguments" to this component-instance.
What I try to accomplish is: Adding my custom component and giving it some data which should be filled into the components Obsverable. At a later (save) action I want to be able to retrieve these data from my "main" view.
My structure is something like this:
main.js
exports.loaded = function(args) {
var page = args.object;
page.bindingContext = model;
var outerContainer = page.getViewById('outerContainer');
var vma = builder.load({
path: 'widgets/durationRow',
name: 'durationRow'
});
outerContainer.addChild(vma);
};
custom component "durationRow.js"
var componentModel = new Observable({
label: "",
start: "",
end: "",
duration: ""
});
exports.onLoad = function(args){
var container = args.object;
container.bindContext = componentModel;
};
I tried to export the componentModel via exports.componentModel and address vma.componentModel from main. But vma is a view not the "JS-module".
I am really stuck and didn't find any example doing something like my scenario.
Can anyone point me in the right direction how I can do this stuff the right (or any) way?
thanks!
So the answer to that problem is quite simple: read the documentation :)
There is an option attributes which can be used exactly for that. I ended up with this:
var row = builder.load({
path: 'widgets/durationRow',
name: 'durationRow',
attributes: {
bindingContext: bindingModel
}
});
container.addChild( row );
And in the loaded widget I'll get my "inserted" model as bindingContext:
exports.onLoad = function(args){
page = args.object;
var model = page.bindingContext;
};
I hope this helps!