Passing Data Between Controllers While Navigating - sapui5

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”));

Related

Calling one controller's method in another controller?

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(..);

Why local Unique IDs inconsistent with IDs in HCP?

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

How to set ID of a XML-View in a component environment?

I want to access a view's controller from a custom module with some utility functions. Basically you can do this that way:
var oController = sap.ui.getCore().byId("__xmlview1").getController();
The problem is that the above coding will not work in a real environment because __xmlview1is dynamically created by the framework. So I tried to find a possibility to set the ID of the view during instantiation. The problem is - I could not find one:
Trying to give the view an ID in the view.xml file does not work:
<mvc:View
controllerName="dividendgrowthtools.view.dividendcompare"
id="testID"
xmlns="sap.m"
...
Trying to set the ID in the router configuration of the component does not work either:
...
name: "Dividend Compare",
viewId: "test",
pattern: "Dividend-Compare",
target: "dividendcompare"
...
The problem is that I do not have direct control over the instantiation of the XML view - the component respectively the router does it.
So, is there a solution for that problem? Or at least a save way to get the view ID by providing the name of the view?
You should have a look at the SAPUI5 EventBus.
I am pretty sure, you want to let the controller to do something with the dividentcompare view. With the SAPUI5 Eventbus, you can publish actions from one controller to another witout braking MVC patterns.
In your dividendcompare.controller.js:
onInit : function() {
var oEventBus = sap.ui.getCore().getEventBus();
oEventBus.subscribe("MyChannel", "doStuff", this.handleDoStuff, this);
[...]
},
handleDoStuff : function (oEvent) {
var oView = this.getView();
[...]
}
Now, in your anothercontroller.controller.js:
onTriggerDividendStuff : function (oEvent){
var oEventBus = sap.ui.getCore().getEventBus();
oEventBus.publish("MyChannel", "doStuff", { [optional Params] });
}
You are now able to get the view from the dividentcontroller in every case from every other controller of your app. You dont access the view directly, this would brake MVC patterns, but can pass options to its controller and do the handling there.

Best practices when editing form with emberjs

Is there any good solutions when editing a form to save the model only if the user clicked on the save button and retrieve the old datas if the user canceled the action ?
I've seen some solutions like duplicating the object that is data-binded with each form fields and set the the initial object with the duplicated one when it is saved.
If you could give answers without using ember data could be great.
I understand you would prefer a solution that doesn't use ember-data, but I would argue that using ember-data is best practices. Here is a solution using ember-data because I imagine a lot of people may come across this question...
If you set up your route as follows, it will do exactly that.
App.CommentEditRoute = Em.Route.extend({
model: function(params) {
return this.store.find('comment', params.comment_id);
},
actions: {
willTransition: function(transition) {
var model = this.get('controller.content');
if (model.get('isDirty')) {
model.rollback();
}
}
},
});
If you call this.get('content').save() in the controller (because the user clicked the save button) it will persist the changes through the adapter and isDirty will be set to false. Thus, the model will not rollback. Otherwise, if you did not call this.get('content').save() in the controller, the isDirty property will be true and the unsaved changes will be discarded. See the DS.Model docs for more info.
willTransition is an event automatically called when the route is about to change - you don't have to call it directly.
Your controller might look like this:
App.CommentEditController = Em.ObjectController.extend({
save: function() {
var _this = this;
_this.get('content').save().then(function() {
// Success
_this.transitionToRoute('comments');
}, function() {
// Failure
Em.assert('Uh oh!');
});
},
cancel: function() {
this.transitionToRoute('comments');
},
});
Also, be sure to utilize the default HTML form submission using a proper HTML button or input for submission so you can capture the submission event in your view as follows:
App.CommentEditView = Em.View.extend({
submit: function() {
this.get('controller').save();
},
});

Get passed data on next page after calling "to" from NavContainer

I am on my way building a Fiori like app using SAPUI5. I have successfully built the Master page, and on item click, I pass the context and navigate to Detail page.
The context path from Master page is something like /SUPPLIER("NAME"). The function in App.controoler.js is as follows:
handleListItemPress: function(evt) {
var context = evt.getSource().getBindingContext();
this.myNavContainer.to("Detail", context);
// ...
},
But I would like to know how I can access this context in the Detail page. I need this because I need to use $expand to build the URL and bind the items to a table.
There is an example in the UI5 Documentation on how to deal with this problem using an EventDelegate for the onBeforeShow function which is called by the framework automatically. I adapted it to your use case:
this.myNavContainer.to("Detail", context); // trigger navigation and hand over a data object
// and where the detail page is implemented:
myDetailPage.addEventDelegate({
onBeforeShow: function(evt) {
var context = evt.data.context;
}
});
The evt.data object contains all data you put in to(<pageId>, <data>). You could log it to the console to see the structure of the evt object.
Please refer the "shopping cart" example in SAP UI5 Demo Kit.
https://sapui5.hana.ondemand.com/sdk/test-resources/sap/m/demokit/cart/index.html?responderOn=true
Generally, in 'Component.js', the routes shall be configured for the different views.
And in the views, the route has to be listened to. Please see below.
In Component.js:
routes: [
{ pattern: "cart",
name: "cart",
view: "Cart",
targetAggregation: "masterPages"
}
]
And in Cart.controller.js, the route has to be listened. In this example, cart is a detail
onInit : function () {
this._router = sap.ui.core.UIComponent.getRouterFor(this);
this._router.attachRoutePatternMatched(this._routePatternMatched, this);
},
_routePatternMatched : function(oEvent) {
if (oEvent.getParameter("name") === "cart") {
//set selection of list back
var oEntryList = this.getView().byId("entryList");
oEntryList.removeSelections();
}
}
Hope this helps.