SAP UI5: Fill XML-Dropdown dynamically using Lifecycle Hook Function - javascript

I've done the Tutorial for building Fiori-like UIs with SAPUI5 and tried adding a sort-function.
The Dropdown-Box is filled with the Names of the JSON-Model-Data:
{
"SalesOrderCollection": [
{
"SoId": "300000097",
"ApprovalStatus": "",
"Status": "Initial",
"ConfirmationStatus": "",
"BillingStatus": "Initial",
...
} ...
I implemented this function in my View Controller to fill the dropdown:
fillDropdown : function(evt) {
// Get current view and dropdownbox
var oView = this.getView();
var oDropdown = oView.byId("ddbox");
// Get names of nodes in model
var metaArray = Object.getOwnPropertyNames(oView.getModel()
.getProperty("/SalesOrderCollection/0"));
// Add every name to dropdownbox
var arrayLength = metaArray.length;
for ( var i = 0; i < arrayLength; i++) {
oDropdown.addItem(new sap.ui.core.ListItem("item" + i)
.setText(metaArray[i]));
}
}
Finally, here comes my problem:
How can I run this function automatically when the View gets rendered? I know the lifecycle hook functions onInit & onBeforeRendering but I can't use them in my XML-View:
I can register the eventhandler for UI-Elements like here:
<uicom:DropdownBox id="ddbox" editable="true" change="handleListSort">
But not for the View or the Page like I tried here:
<Page title="myPage" onBeforeRendering="fillDropdown">
<core:View controllerName="sap.ui.demo.myFiori.view.Master" (...) onBeforeRendering="fillDropdown">
Possible dirty workaround: Call the function when clicking on the Sort-Button in the IconTabBar
<IconTabBar select="fillDropdown">
Thanks for your help!
Update
If I used a JavaScript-View instead of an XML-View, I could simply implement my fillDropdown function in the onAfterRendering function of my View.
But why do XML Views not throw lifecycle hook events?
Update 2
I also can't use the onAfterRendering of my View Controller; oView.getModel().getProperty("/SalesOrderCollection/0"); returns no object because the content of my model is (whyever) only available after the hook functions.

onInit, onBeforeRendering, onAfterRendering, onExit are lifecycle events. You do not need to register from view. Your function should be called when you implement independent from the view type. Please have a look at here http://jsbin.com/hiyanuqu/1/edit
BTW, there is a similar built in control in sap.m library called Input with Suggestion.
https://openui5.hana.ondemand.com/test-resources/sap/m/demokit/explored/index.html#/sample/sap.m.sample.InputSuggestionsCustomFilter

Related

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:
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
});
})
}

Rails 5 javascript for Ajax controlled partial is acting strangely & it can't initialize bootstrap-star-rating inputs as it should

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

How to filter a list via controller in sapui5

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

Analogues $apply or $digest(Angular) in Aurelia

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".

NativeScript UI-Builder

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!

Categories

Resources