How does the lifecycle of UI5 Controls work? - sapui5

Can someone give a more detailed explanation about the lifecycle of the default events of a UI5 Control? I know there is this page on the documentation that gives an overview of a Control lifecycle, however, I think it is very brief and wanted something more detailed. Can someone list the order of the events of a Control and explain what every event does?

You are absolutely right. The details of a Control lifecycle and implementation details are very well hidden in the docs. I'll try to sum up my so far understanding for you.
The lifecycle of a Control is mainly determined by:
init : Your little Control is born! Function is called by the framework during constructor execution. Do your initialization stuff here.
onBeforeRendering : Called by the framework before the rendering of the control is started. Triggers before every (re)rendering.
onAfterRendering : Called by the framework after the rendering of the control has completed. Triggers after every (re)rendering.
exit : RIP little Control! Cleans up the element instance before destruction. Called by the framework. Do your clean up here. Btw: If you need to explicitly destruct a Control/Element you should call destroy and not directly exit.
Here is a sample implementation with some sample usages for the different hooks:
sap.ui.core.Control.extend("a.sample.Control", {
init : function() {
// instantiate a sub-control
this._btn = new sap.m.Button();
},
onBeforeRendering : function() {
// deregister a listener via jQuery
this.$("subelement").off("click", this.subElementClick);
},
onAfterRendering : function() {
// register a listener via jQuery on a sub-element
this.$("subelement").on("click", this.subElementClick);
},
subElementClick : function() {
// do stuff
},
exit : function() {
// clean up sub-controls and local references
this._btn.destroy();
delete this._btn;
}
});
Why shouldn't I do my init stuff in my constructor?
There is a basic UI5 constructor in ManagedObject. It "prepares" your UI5 object for you and calls your init function afterwards. That means in your init all settings will already be applied for you and you can access properties and aggregations as usual.
Why shouldn't I call rerender?
The SAPUI5 rendering is intelligent in a sense that it groups and optimizes queued rerenderings. Therefore you should never call rerender directly but instead use invalidate to mark a control for rerendering.
HF
Chris

UI5 provides predefined lifecycle hooks for Controller implementation. You can add event handlers or other functions to the controller and the controller can fire events, for which other controllers or entities can register.
UI5 provides the following lifecycle hooks:
onInit(): Called when a view is instantiated and its controls (if available) have already been created; used to modify the view before it is displayed to bind event handlers and do other one-time initialization
onExit(): Called when the view is destroyed; used to free resources and finalize activities
onAfterRendering(): Called when the view has been rendered and, therefore, its HTML is part of the document; used to do post-rendering manipulations of the HTML. SAPUI5 controls get this hook after being rendered.
onBeforeRendering(): Called every time the View is rendered, before the Renderer is called, and the HTML is placed in the DOM-Tree.
Source: ui5.sap.com/#/topic/121b8e6337d147af9819129e428f1f75

Related

Angular2 redirect from static method

Hi i have 'static class' Utils with only static methods (helpers):
export class Utils {
static doSomethingAndRedirect() {
...doo something...
redirectTo->'/home'
}
}
So how redirectTo code should look like?
You have a wrong approach to the usecase you are trying to solve. Have a look at ngrx/store and ngrx/effects.
In short, you define actions and reducers which modify the state of your app. Next, you can react to different actions with different side-effects (ngrx/effects), for example in my app I have got:
Actions: LoginAction and LoginSuccessAction
Effects: when LoginSuccessAction is triggered, my effect redirects to /dashboard component
This makes for nice separation of concerns:
views display the current state and dispatch actions that change the state
actions specify what happens in the app
reducers specify what changes to state occur for different actions
effects specify what side-effects occur for certain actions
The best solution that I found in my case was just... add parameter 'router' into static function argument list:
static doSomethingAndRedirect(router) {
...doo something...
router.navigateByUrl('home'); // redirect
}
This is a kind of compromise between static helper convenience and non-static "angular way".

Global Model Not Accesible

I declared a model in Component.js of a UI5 application as below
init: function() {
sap.ui.core.UIComponent.prototype.init.apply(this);
var oModel1 = new sap.ui.model.json.JSONModel("model/mock.json");
sap.ui.getCore().setModel(oModel1, "oModelForSales");
},
but was not able to access the model in any of the onInit methods inside controllers unless the model is set on view instead as below:
var oModel1 = new sap.ui.model.json.JSONModel("model/routes.json");
this.getView().setModel(oModel1);
The log for sap.ui.getCore().getModel("oModelForSales") in controllers onInit shows the model as undefined but I was able to fetch it in onBeforeRendering handler.
Why are core models, set in Component.js, not accessible in onInit?
Avoid setting models on the Core directly if you're using Components. Components are meant to be independent and reusable parts and therefore will not inherit the Core models by default. Models should be set depending on your business case:
Models declared in the app descriptor (manifest.json) section /sap.ui5/models will be set on the Component. They are automatically propagated to its descendants. Given default model, the following returns true:
this.getOwnerComponent().getModel() === this.getView().getModel() // returns: true
Note: calling this.getView().getModel() in onInit will still return undefined since the view doesn't know its parent at that moment yet (this.getView().getParent() returns null). Therefore, in onInit, call the getModel explicitly from the parent that owns the model. E.g.:
{ // My.controller.js
onInit: function {
// The view is instantiated but no parent is assigned yet.
// Models from the parent aren't accessible here.
// Accessing the model explicitly from the Component works:
const myGlobalModel = this.getOwnerComponent().getModel(/*modelName*/);
},
}
Set models only on certain controls (e.g. View, Panel, etc.) if the data are not needed elsewhere.
Set models on the Core only if the app is not Component-based.
If Core models or any other model from upper hierarchy should still be propagated to the Component and its children, enable propagateModel when instantiating the ComponentContainer.
new ComponentContainer({ // required from "sap/ui/core/ComponentContainer"
//...,
propagateModel: true // Allow propagating parent binding and model information (e.g. from the Core) to the Component and it's children.
})
But again, this is not a good practice since Core models can be blindly overwritten by other apps on FLP as SAP recommends:
Do not use sap.ui.getCore() to register models.
About the Core model being undefined in onInit: This is not the case anymore as of version 1.34.0. The Core model can be accessed from anywhere in the controller. However, descendants of ComponentContainer are unaware of such models by default as explained above.
You should not set the Model to the Core.
Instead set it to the Component. That way the Controllers and Views belonging to that Component will be able to access it.
Adding more info on this:
During onInint of a controller, the view/controllers do not know their parent as where would they land, and hence they can not refer to the model.
However, this can be achieved from the following code:
this.getOwnerComponent().getModel()
As the component is already initialized and should return the model.
can you once try this code -
init:function(){
//sap.ui.core.UIComponent.prototype.init.apply(this);
var oModel1 = new sap.ui.model.json.JSONModel("model/mock.json");
sap.ui.getCore().setModel(oModel1,"oModelForSales");
console.log(sap.ui.getCore().getModel("oModelForSales"));
sap.ui.core.UIComponent.prototype.init.apply(this);
},
and then in you init method of any controller try -
console.log(sap.ui.getCore().getModel("oModelForSales"));
I think sap.ui.core.UIComponent.prototype.init.apply(this);->calls the create content methods and your view and controllers are initialised even before your model is defined, hence you get undefined as model. Using my approach, we create the model first and then call the super init method in Component.
Note #Admins-> I dont have enough points to comment, so adding an answer.

dart js-interop FunctionProxy callback relationship to js.context

I have a Dart js-interop callback that in turn takes a javascript callback as an argument. The dart callback implementation looks like this:
void callBackToDartCode(String query, js.FunctionProxy completionCallback) {
js.context.completionCallback = completionCallback;
doSomethingAscyn(query).then(
(result) {
// hand the query result back to the javascript code
js.context.completionCallback(js.map(result));
});
This works. The key to making this work is to save the FunctionProxy in the js.context so that it is available when it comes time to execute it in the async "then" method. This line of code is important:
js.context.completionCallback = completionCallback;
If that's not done then the completeCallback is not retained and hence cannot be called when the async operation completes.
I have not seen examples like this and I am not sure I have really done this properly.
It raises questions:
How do I disassociate "completeCallback" from js.context after I've called it? Does it remain associated with js.context forever?
It appears there will be conflicting use of the name "completionCallback" within js.context if multiple async operations are in progress at the same time. That strikes me as a common problem. Does js-interop have a way to deal with that or is it my job to manage that?
With js-interop all proxies are scoped to prevent memory leaks. This means that Proxy will lost its JS object reference at the end of its associated scope. If scoped((){}) function is not use explicitely a lazy scope is initialized the first time an interop operation is done and the scope is automatically closed at the end of the current event loop. If you want to make a Proxy to live longer than its associated scope, you have to retain it. This can be done with js.retain(proxy). Once your proxy is no longer needed, you can release it with js.release(proxy).
Thus your code should be :
void callBackToDartCode(String query, js.FunctionProxy completionCallback) {
js.retain(completionCallback);
doSomethingAscyn(query).then(
(result) {
// hand the query result back to the javascript code
completionCallback(js.map(result));
// completionCallback is no longer used
js.release(completionCallback);
});
}
About your question about disassociate "completeCallback" from js.context you could have done it with js.deleteProperty(js.context, "completeCallback")

Using layouts from others classes in vaadin

Is there any idea wich allows me using layouts declared in MyApplication.java from other classes and functions.
I tried put them in parameters it works but it becomes very complicated
For example xhen callin a function named Y in function X I have to pass all layouts on parameters like this:
X(layout1,layout2,layout3,layout4)
{
Y(a,b,c,layout1,layout2,layout3,layout4)
}
I tried to use a class named uiHelper but it didn't works
You can take a look at Blackboard addon for vaadin.
https://vaadin.com/addon/blackboard
From that page:
Sometimes, having a deep component hierarchy poses a problem, when you need to inform a component high up in the tree that something happened deep down below. You normally have one of two choices - either pass the listener all the way down the hierarchy, leading to more coupled code, or let each component in between be a listener/notifier, passing the event all the way back up. With the Blackboard, you can register any listener to listen for any event, and when that event is fired, all the listeners for that event are triggered. This keeps your components clean and rid of unnecessary boilerplate code.
For your example, you can create a LayoutChangeListener and LayoutChangeEvent.
MyApplication can then implements LayoutChangeListener and when a LayoutChangeEvent is fired, you can change your layout without passing it around.

GWT - Where should i use code splitting while using places/activities/mappers?

"core" refers to the initial piece of the application that is loaded.
In order to bind url to places, GWT uses PlaceTokenizer<P extends Place>. When loading the application from the url, it calls the method P getPlace(String token) to retrieve a new instance of the place to call.
due to the asynchronous nature of code splitting, I can't create the place inside a runAsync in this method. So I have to put all the places of my app in the core.
To link places to activity, GWT callsActivity getActivity(Place place) (from com.google.gwt.activity.shared.ActivityMapper) to retrieve a new instance of the activity.
Once again, i have to put all my activities in the core.
Here's what I want to try: Write a custom com.google.gwt.place.shared.Delegate that
bind itself on PlaceChangeRequestEvent. If the AppPiece corresponding to the requestedPlace isn't loaded, it calls event.setWarning(NEED_TO_LOAD_MODULE)
in the confirm(String message) method, always return false when the message equals NEED_TO_LOAD_MODULE (so it doesn't bother the user), and load the module via RunAsync.
Once the module is loaded, call goTo(requestedPlace)
Each AppPiece of my application contains a bunch of activies and the corresponding views. Since the mappers are only called when PlaceChangeEventis fired, i could generate a new instance of my activity via AppPiece.getSomeActivityInstance().
I'm pretty sure this will work, but what bother me is that
Finding wich AppPiece to load depending on the requestedPlace will force me to write code that will be very similar to my mappers
I would like to have my places inside the corresponding AppPiece
Overriding Delegate for this purpose is tricky, and I'm looking for a better solution
You don't have to put all your activities in the core (as you call it): while an Activity instance is retrieved synchronously, it's allowed to start asynchronously. This is where you'd put your GWT.runAsync call.
See http://code.google.com/p/google-web-toolkit/issues/detail?id=5129 and https://groups.google.com/d/topic/google-web-toolkit/8_P_d4aT-0E/discussion