BusyIndicator for navigating between pages - sapui5

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

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

Composition instead of inheritance in Controllers

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

Event for view leave

I declared a controller for a view in my SAPUI5 application. Now I want to perform tasks when the view is left by the user.
There is already a possibility to add a callback function to attachRoutePatternMatched to perform tasks when the view is navigated by the user now I need a equivalent function to handle a leave of the view. I use a SplitContainer as parent container
onInit: function() {
this._oRouter = this.getOwnerComponent().getRouter();
this._oRouter.attachRoutePatternMatched(this._routePatternMatched, this);
},
_routePatternMatched: function(oEvent) {
var that = this;
var sRouteTargetName = oEvent.getParameter("name");
if (sRouteTargetName === "myView") {
// perform tasks if the view is opened by the user
}
},
You can try if this works:
navAway: function(viewName, callback) {
this._oRouter.navTo(viewName);
if(callback && typeof(callback) === "function") {
callback();
}
}
e.g. this.navAway("myView", function() { //doStuff });
Presume you mean navigating backwards? If you have a back button, which presumably you must, put your actions in that function. E.g your detail/master has a navBack button in the toolbar, so put your logic in the button's event handler...
You can achieve this with BeforeHide delegate on the NavContainer child which is often the view:
onInit: function() {
this._navDelegate = { onBeforeHide: this.onBeforeLeave };
this.getView()/*<-- navContainerChild*/.addEventDelegate(this._navDelegate, this);
},
onBeforeLeaving: function(event) {
// ... do something
},
onExit: function() {
// detach events, delegates, and references to avoid memory leak
this.getView().removeEventDelegate(this._navDelegate);
this._navDelegate = null;
},
Example: https://embed.plnkr.co/wp6yes?show=controller%2FNext.controller.js,preview%23next
API reference: NavContainerChild
API reference: sap.ui.core.Element#addEventDelegate
For other navigation related events, see documentation topics mentioned in https://stackoverflow.com/a/55649563

Uncaught TypeError: Cannot read property 'setVisible' of undefined

Im fairly new to SAPUI5 and when I click on button I get the error in the title
what I did in Is I used the SAP web IDE to create new MVC project .
in the main view JS I put
createContent : function(oController) {
var btn = new sap.m.Button({
id:"myBtn",
text : "Content Button"
});
return new sap.m.Page({
title: "TitleT",
content: [ btn ]
});
}
in the Main controller JS I put the following code
onInit: function() {
var that = this;
window.setTimeout(function() {
that.byId("myBtn").setVisible(true);
}, Math.random() * 10000);
},
onPress: function() {
this.byId("pressMeButton").setText("I got pressed");
}
When I run it I see the button but when I click on it I get the error in the on Init,
what am I doing wrong here?
The actual problem with your code is that you create a static id in your javascript view, but the controller will search the id with a prefix like "__jsview0--myBtn" if you call that.byId("myBtn").
Therefore you either have to use createId("myBtn") in your javascript view for defining the id or sap.ui.getCore().byId("myBtn") in the controller and it will work fine. The first approach is recommended though to avoid name clashes.
PS:
i did not really get the use case, it seems like you want to display the button only after a certain (random) timeframe. But the visible flag by default is already true, so the button will always be visible.
Use the standard timeout and byId function from SAPUI5 like this:
onInit: function() {
setTimeout(function() {
sap.ui.getCore().byId("myBtn").setVisible(true);
}, Math.random() * 10000);
},

Kendo layouts not rendering widgets without setTimeout

I upgraded the kendo library to the 2014Q1 framework which had a few nice features that they were adding, however when I did that it broke any widget (grid, tabStrip, select lists, etc.) from rendering at all. I tracked it down to the layout/view not being able to activate the widget without being wrapped in a setTimeout set to 0. Am I missing something key here or did I build this thing in an invalid way?
http://jsfiddle.net/upmFf/
The basic idea of the problem I am having is below (remove the comments and it works):
var router = new kendo.Router();
var mainLayout = new kendo.Layout($('#mainLayout').html());
var view = new kendo.View('sample', {
wrap: false,
model: kendo.observable({}),
init: function() {
// setTimeout(function(){
$("#datepicker").kendoDatePicker();
// }, 0);
}
});
mainLayout.render('#container');
router.route('/', function() {
mainLayout.showIn('#app', view);
});
router.start();
Admittedly, I don't fully understand it, but hope this helps.
Basically when you try to init the #datepicker, the view elements have not been inserted into the DOM yet. You can put a breakpoint inside the init function, when it hits, check the DOM and you will see that the #app is an empty div, and #datepicker does not exist yet (at least not on the DOM).
kendo.Layout.showIn seems to need to exit in order for the view to finish rendering, but when it initializes the view's elements, it thinks the render is done and init is triggered incorrectly ahead of time. The setTimeout works because it runs the kendoDatePicker initialization asynch, the view is able to finish rendering before the timeout function.
Workarounds...
Trigger the view rendering from the view object itself:
var view = new kendo.View('sample', {
init: function() {
$("#datepicker").kendoDatePicker();
}
});
router.route('/', function() {
view.render('#app');
});
Select and find the datepicker from the view object itself:
var view = new kendo.View('sample', {
init: function() {
view.element.find("#datepicker").kendoDatePicker();
}
});
router.route('/', function() {
mainLayout.showIn('#app', view);
});
Near the bottom of this thread is where I got the idea for the 2nd option. Maybe someone else can come around and give a better explanation of whats going on.