I am totally confused with all the buzzwords you all are using modules, eventbus.
I will try to rephrase my question in more simple words because I am new to this framework and I like to understand it in simple way. So here is what I am trying to achieve:
I have a Questionnaire controller which is binded to Questionnaire view. Now I need to fetch some data from my backend with my xsjs and bind to this view. I need to fetch this data before the page renders so I am using my ajax call in Before Rendering and in the complete property of my ajax call I need to perform some vaildations. As my function in complete property is too long I was thinking of creating a separate controller and then defining my method which makes ajax call and necessary validations here. This new controller just holds this method definition hence it is not binded to any view.
Now How should I call this controller in the Questionnaire controller and use its method that makes the ajax call and performs the validations in controller method?
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel",
"sap/m/MessageBox"], function(Controller, JSONModel, MessageBox) {
var questionnaireResponseId;
var password;
var backendJSON;
Controller.extend("OnlineQuestionnaire.controller.Questionnaire", {
onInit: function() {
jQuery.sap.require("jquery.sap.storage");
},
onBeforeRendering: function() {
questionnaireResponseId = jQuery.sap.storage.get("QuestionnaireResponseId");
password = jQuery.sap.storage.get("Password");
backendJSON = loadStack(questionnaireResponseId); //This is not correct way to call
}
This method is defined in QuestionStack.controller.js
loadStack(questionnaireResponseId) {
jQuery.ajax({
url: "",
method: "GET",
dataType: "json",
complete: this.onSuccess,
error: this.onErrorCall
});
return output;
}
extend your QuestionStack.controller.js with Questionnare.controller.js:
sap.ui.define([
".." // path to your QuestionStack.controller.js, e.g. "myapp/controller/QuestionStack"
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel",
"sap/m/MessageBox"], function(Controller, JSONModel, MessageBox) {
var questionnaireResponseId;
var password;
var backendJSON;
QuestionStack.extend("OnlineQuestionnaire.controller.Questionnaire", { // extend
..
}
call the method with this.loadStack(..);
Related
I am quite new to SAPUI5 and still learning a lot.
Actually I am trying to set up a busydialog while navigating between two different views.
I already defined the busydialog and set it up on a press-Event after one hits the navigation button. The dialog is showing up, but Iam not really sure about the handling regarding the close event. I thought that onMatchedRoute could help me, but the dialog is not closing. My controller for the first page looks like:
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/core/format/NumberFormat",
"sap/m/BusyDialog"
], function(Controller, NumberFormat) {
"use strict";
var BusyDialogGlobal;
return Controller.extend("sap.turbo.ma.mc.controller.region.americas.AmFinance", {
onInit: function() {
BusyDialogGlobal = new sap.m.BusyDialog("GlobalBusyDialog",{title:"Please wait. . . "});
onHomePress: function() {
var oRouter = this.getOwnerComponent().getRouter();
oRouter.navTo("home");
var getDialog = sap.ui.getCore().byId("GlobalBusyDialog");
getDialog.open();
This part is working. I am not sure about further process handling part to close the busydialog after the second page/view is loaded. Maybe someone has a small snippet or example that could help me out here?
You are not defining your 'BusyDialogGlobal' in a global scope. It is defined in the controller scope for this view. Therefore in your second view probably you cant access it.
Two approaches you can do here (in the order I think are better):
Option 1
Build all your controllers extending a custom 'Base Controller', this way you will have access to its functions from all the children controllers. To do so, create a normal controller, extending from sap/ui/core/mvc/Controller as you have done. Call it 'BaseController' for example and save it in your controllers folder. Then create all your view controllers extending from the BaseController. Something like this:
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function (Controller) {
"use strict";
return Controller.extend("mynamespace.controller.BaseController", {
onInit: function(){
this.BusyDialogGlobal = new sap.m.BusyDialog("GlobalBusyDialog",{title:"Please wait. . . "});
},
presentBusyDialog(){
this.BusyDialogGlobal.open()
}
dismissBusyDialog(){
this.BusyDialogGlobal.close()
}
}
});
Then in your view1.controller.js:
sap.ui.define([
"mynamespace/controller/BaseController"
], function (BaseController) {
"use strict";
return BaseController.extend("mynamespace.controller.View1", {
onNavigate: function(){
//Do your navigation logic here
//...
this.presentBusyDialog();
}
}
});
And in your View2.controller.js
sap.ui.define([
"mynamespace/controller/BaseController"
], function (BaseController) {
"use strict";
return BaseController.extend("mynamespace.controller.View2", {
onInit: function(){
this.dismissBusyDialog();
}
}
});
Option 2
Easier but less elegant, just instantiate the busyDialog in your Component scope and retrieve it from that scope when needed.
To instantiate it from a view controller:
this.getOwnerComponent().BusyDialogGlobal = new sap.m.BusyDialog("GlobalBusyDialog",{title:"Please wait. . . "});
To open it from whatever view controller when needed:
this.getOwnerComponent().BusyDialogGlobal.open()
To close it from whatever view controller when needed:
this.getOwnerComponent().BusyDialogGlobal.close()
I want to pass data between two controllers (in addition to routing parameters) and I would like to know the correct way to do this.
For example: when I navigate to pattern /order/{id}, I do this in the view controller:
this.getRouter().navTo("order", {
id: sOrderId
});
I want to pass additional JSON object which I don't want to be part of routing parameter.
What should I do in this case?
--edit
Wanted to add what I like to achieve with this
I want pass data from master to detail. Both master and detail page has individual routing patterns assigned. So user can land on master or detail directly. When they land on master - user can choose bunch of detail items, and navigate to first detail item, and from there navigate to other items he/she selected earlier on master. So what I want to pass is this selection from master controller to detail controller.
Note: If the intention is to pass selected keys from the main view to the detail view, see https://stackoverflow.com/a/48870579/5846045 instead.
Using a client-side model
Usually, data are stored separately in models instead of assigned to local variables and passing them around. Model data can be then shared with anything that can access the model (e.g. View for data binding).
Here is an example with a client-side model (JSONModel):
Create a JSONModel which is set on a parent ManagedObject. E.g. on the Component via manifest.json:
"sap.ui5": {
"models": {
"myModel": {
"type": "sap.ui.model.json.JSONModel"
}
}
}
In the controller A, set the object to pass before navigating:
const dataToPass = /*...*/
this.getOwnerComponent().getModel("myModel").setProperty("/data", dataToPass, null, true);
In the controller B, do something with the passed data. E.g. on patternMatched handler:
onInit: function() {
const orderRoute = this.getOwnerComponent().getRouter().getRoute("order");
orderRoute.attachPatternMatched(this.onPatternMatched, this);
},
onPatternMatched: function() {
/*Do something with:*/this.getOwnerComponent().getModel("myModel").getProperty("/data");
},
Using NavContainer(Child) events
There are several navigation-related events such as navigate,
BeforeHide, BeforeShow, etc. which contain both views - the source view (from) and the target view (to).
You can make use of the API data to pass the data.
Here is an example:
In the controller A:
onInit: function() {
this.getView().addEventDelegate({
onBeforeHide: function(event) {
const targetView = event.to;
const dataToPass = /*...*/
targetView.data("data", dataToPass);
}
}, this);
},
In the controller B:
onInit: function() {
this.getView().addEventDelegate({
onBeforeShow: function(event) {
/*Do something with:*/this.getView().data("data");
}
}, this);
},
See also the related documentation topic: Passing Data when Navigating
You can create a local model (usually a JSONModel) and set it to inside your app Component.
// inside Component.js
var model = new sap.ui.model.json.JSONModel({ foo: “bar”});
this.setModel(model);
Inside each controller you can use
var model = this.getOwnerComponent().getModel();
console.log(model.getProperty(“/foo”));
I would like to check for data changes when the user clicks on the back button in the Fiori Launchpad. I have the following code
onAfterRendering: function() {
sap.ui.getCore().byId("backBtn").attachPress(this, function(oEvent) {
oEvent.preventDefault();
});
}
Within the function I would like to access the oData and other variables of the main controller. However when I press the back button the "this" object is the header control's view.
How can get the view of the page content and also access the oData and other parameters of the controller associated to the content view.
In order to access the current context you have to call the event handler function within that specific context, so therefore a binding is needed on that function.
onAfterRendering: function() {
sap.ui.getCore().byId("backBtn").attachPress(this, function(oEvent) {
oEvent.preventDefault();
}.bind(this));
}
If usage of sap.m.Page is an option than navButtonPress builtin event can be used:
View.xml:
<mvc:View xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc">
<Page navButtonPress="onNavBack">
...
Controller.js:
onNavBack: function(oControlEvent) {
var oController = this;
var oView = this.getView();
}
the event listener will be triggered every time the button is pressed in the Fiori Launchpad header.
I have inspect the element in app that run local in HCP, the id is application-MaintainMasterData-display-component---addRoute--form, but when I deploy to cloud, the id changed to application-MaintainFleet-Display-component---addRoute--form
The app name changed, and the display in the upper class way, which makes my sap.ui.getCore().byId() failed in cloud. I was confussing why this happens.
I've read the ref, I was in a Event handler, I need the oEvent scope, so this.getView().byId() and this.createId() won't works for me.
Ref:
sap.ui.getCore().byId() returns no element
https://sapui5.netweaver.ondemand.com/sdk/#docs/guide/91f28be26f4d1014b6dd926db0e91070.html
=========UPDATE=========
I also tried sap.ui.getCore().byId("application-MaintainMasterData-display-component---addRoute").byId("form") , but the same issue , view id is application-MaintainFleet-Display-component---addRoute in cloud.
The IDs are dynamically generated. So you cannot rely on them. That's why you should not use sap.ui.getCore().byId(). Even the separators -- and --- may change in the future.
You should always use the byId() method of the nearest view or component to resolve local ids. You can chain the calls: component.byId("myView").byId("myControl")
In your eventhandler this should refer to the controller. For XMLViews this should be the case without further doing.
So i guess you are using JSViews? If you attach an eventhandler in code you can always supply a second argument to the attachWhatever() functions: The Object that becomes this in the event handler.
Controller.extend("myView", {
onInit:function(){
var button = this.byId("button1");
button.attachPress(this.onButtonPress, this); //The second parameter will become 'this' in the onButtonPress function
},
onButtonPress: function(oEvent){
console.log(this); //this is the controller
var buttonPressed = oEvent.getSource(); //get the control that triggered the event.
var otherControl = this.byId("table"); //access other controls by id
var view = this.getView(); //access the view
}
});
If you are using the settings-object-syntax you can supply an array for the events. It should contain the handler function and the object that should become this:
createContent:function(oController){
return new Button({
text: "Hello World",
press: [
function(oEvent){ console.log(this); }, //the event handler
oController //oController will be 'this' in the function above
]
});
If you are attaching to a non-UI5-event you can always use a closure to supply the view or controller to the handler function:
onInit:function(){
var that = this; //save controller reference in local variable
something.on("event", function(){ console.log(that); });
//you can use that local variable inside the eventhandler functions code.
}
I'd like to implement a structure like below to avoid a "BaseController" superclass:
controller/helpers/Navigation.js
sap.ui.define([
"sap/ui/base/Object"
], function(Object) {
"use strict";
return Object.extend("sap.cre.core.ui.controller.helpers.Navigation", {
controller: null,
onInit: function(controller) {
this._controller = controller;
},
onNavBack: function() {
this._controller.getRouter().navTo("home");
}
});
});
controller/Something.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/cre/core/ui/controller/helpers/Navigation"
], function(Controller, Navigation) {
"use strict";
return Controller.extend("sap.cre.core.ui.controller.Something", {
onInit: function() {
this.navigation = new Navigation(this);
}
});
});
Then the XML view is pointing the onPress event like below:
<semantic:FullscreenPage
navButtonPress="navigation.onNavBack"
showNavButton="true"> ...
But the view isn't finding the event when pointed to navigation..
So, my questions are:
has someone seen such approach over OpenUI5/SAPUI5 already?
Is there any bad consequence?
What is wrong in my approach that the view cannot call navigation.onNavBack?
Thanks!
Update:
I tried this way too:
controller/Something.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/cre/core/ui/controller/helpers/Navigation"
], function(Controller, Navigation) {
"use strict";
return Controller.extend("sap.cre.core.ui.controller.Something", {
navigation: new Navigation()
});
});
That way works as #hirse suggested (putting a . in front of the path in the view), but that let me lose any link to the controller, which turns the helper class too limited and mostly useless.
But it makes me guess that just by setting this.navigation = new Navigation(this) it's missing anything about binding on the way.
Your approach looks interesting and I think it could be a good idea.
Now to your concrete problem:
You are missing a . in front of your handler function: .navigation.onNavBack
Quoting from the Developer Guide:
Names starting with a dot ('.') are always assumed to represent a method in the controller.
Names containing a dot at a later position are assumed to represent global functions
Names without dot are interpreted as a relative name; if nothing is found, they are interpreted as an absolute name.
Which means,
onNavBack will use the function in the controller if there is one and keep looking otherwise;
.onNavBack will expect a function in the controller;
navigation.onNavBack will only look for a global function;
.navigation.onNavBack is what you want to use.
I think the following call cannot work:
this.navigation = new Navigation(this);
This can't work because your helper sap.cre.core.ui.controller.helpers.Navigation does not declare a constructor which could take the one parameter that you are passing (the reference to your controller). I guess you are assuming that new Navigation(this); will call your onInit(...) and due to some magic the reference to your controller is passed. But that's not right. Make sure to declare a constructor, see for sap.ui.base.Object for details. In your case any call to onNavBack will lead to an error because your this._controller is undefined, right?
I have used a similar approach in the past together with sap.ui.define. But instead of returning sap.ui.base.Object.extend(...) I used native OO JavaScript...
That helped me to get rid of sap.ui.base.Object. However, you might have a good reason to keep that dependency...
All you have to do is redefine the constructor of Navigation.js
constructor: function(controller) {
this._controller = controller;
},
Now you have a reference to the main controller...