I am trying to use a helper class to invoke a method in xml view as detailed in documentation
https://sapui5.hana.ondemand.com/#/topic/b0fb4de7364f4bcbb053a99aa645affe
<Button text="Press Me" press="ZUI5.ZTESTAPP.TestClass.handlePress.call($controller, 'Hello World')"/>
However the object does not get resolved in jQuery.sap.getObject (returns undefined).
Here is the helper class code
sap.ui.define(["sap/ui/base/Object"],
function (Object) {
"use strict";
var o = Object.extend("ZUI5.ZTESTAPP.TestClass", {
constructor: function(){
},
initalize: function(oView){
this._view = oView;
},
handlePress: function(oEvent){
debugger;
//alert('Message Set');
}
});
return o;
});
This feature is not available in version 1.44.x
Based on the documentation it seems to be introduced in version 1.56 only.
Compare the following documentations
1.56.x - Handling Events in XML Views
1.54.x - Handling Events in XML Views
Related
I'm trying to use the sap.m.Popover as a "rich tooltip" for some controls. This is as per recommendation from SAP because the sap.ui.commons library is now deprecated. We have too much text we want to add to the standard string tooltip. I haven't figured out a way to use the popover directly as a tooltip, which is why I've extended the TooltipBase control to handle the popover.
I've got the popover working fine, However when I interact with my control, I get the following error:
Uncaught Error: failed to load 'myNewToolTip/controls/TooltipBaseRenderer.js' from ../controls/TooltipBaseRenderer.js: 404 - Not Found
I see from these threads that it is because the TooltipBase class is an abstract class and therefore doesn't have a renderer. However, because I'm already using the popover, I don't need to render anything. I've tried to add the TooltipBaseRenderer.js and just have an empty render class. But UI5 really doesn't like that either.
My question is what should I do, I see two options:
There is probably a simple way to use the popover as a tooltip, which I'm just too stupid to figure out (Bear in mind, I'd prefer to bind it directly in the XML view).
Figure out a way to suppress the renderer call as I don't need it.
This is my current source code for the custom control:
sap.ui.define([
"sap/m/Popover"
], function (Popover) {
"use strict";
return sap.ui.core.TooltipBase.extend("myNewToolTip.TooltipBase", {
metadata: {
properties: {
title : {}
},
events: {
"onmouseover" : {},
"onmouseout" : {}
}
},
oView: null,
setView: function(view) {
this.oView = view;
},
onmouseover : function(oEvent) {
var that = this;
if (!this.delayedCall){
this.delayedCall = setTimeout(function() {
if (!that.oPopover){
that._createQuickView();
}
}, 500);
}
},
onmouseout: function(oEvent) {
if (this.oPopover){
this.closePopover();
this.delayedCall = undefined;
}
else{
clearTimeout(this.delayedCall);
this.delayedCall = undefined;
}
},
_createQuickView: function() {
var sTitle = this.getTitle();
this.oPopover = new Popover({
title: sTitle
});
this.oPopover.openBy(this.getParent());
},
closePopover: function(){
this.oPopover.close();
this.oPopover = undefined;
}
});
});
There is no need to create a custom control just to display a popover on mouseover. As you said, there is a simpler way: Adding event delegates.
One of the events that delegates can listen to is onmouseover which can be achieved like this:
this.byId("myTargetControl").addEventDelegate({
onmouseover: function () {
// Open popover here
}
});
Demo: https://embed.plnkr.co/jAFIHK
Extending sap.ui.core.TooltipBase
If you still consider extending TooltipBase (without Popover), take a look at this example: https://embed.plnkr.co/33zFqa?show=control/MyCustomTooltip.js,preview
Keep in mind, though, that tooltips in general shouldn't contain critical information due to its lack of discoverability as Fiori Design Guideline mentions
Tooltips (...) should never contain critical information. They should also not contain redundant information.
Just as a friendly reminder :) Don't make people hover to find things.
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(..);
I have a question regarding how protractor handles the locating of elements.
I am using page-objects just like I did in Webdriver.
The big difference with Webdriver is that locating the element only happens when a function is called on that element.
When using page-objects, it is advised to instantiate your objects before your tests. But then I was wondering, if you instantiate your object and the page changes, what happens to the state of the elements?
I shall demonstrate with an example
it('Change service', function() {
servicePage.clickChangeService();
serviceForm.selectService(1);
serviceForm.save();
expect(servicePage.getService()).toMatch('\bNo service\b');
});
When debugging servicePage.getService() returns undefined.
Is this because serviceForm is another page and the state of servicePage has been changed?
This is my pageobject:
var servicePage = function() {
this.changeServiceLink = element(by.id('serviceLink'));
this.service = element(by.id('service'));
this.clickChangeService = function() {
this.changeServiceLink.click();
};
this.getService = function() {
return this.service.getAttribute('value');
};
};
module.exports = servicePage;
Thank you in advance.
Regards
Essentially, element() is an 'elementFinder' which doesn't do any work unless you call some action like getAttribute().
So you can think of element(by.id('service')) as a placeholder.
When you want to actually find the element and do some action, then you combine it like element(by.id('service')).getAttribute('value'), but this in itself isn't the value that you are looking for, it's a promise to get the value. You can read all about how to deal with promises elsewhere.
The other thing that protractor does specifically is to patch in a waitForAngular() when it applies an action so that it will wait for any outstanding http calls and timeouts before actually going out to find the element and apply the action. So when you call .getAttribute() it really looks like
return browser.waitForAngular().then(function() {
return element(by.id('service')).getAttribute('value');
});
So, in your example, if your angular pages aren't set up correctly or depending on the controls you are using, you might be trying to get the value before the page has settled with the new value in the element.
To debug your example you should be doing something like
it('Change service', function() {
servicePage.getService().then(function(originalService) {
console.log('originalService: ' + originalService);
});
servicePage.clickChangeService();
serviceForm.selectService(1);
serviceForm.save();
servicePage.getService().then(function(newService) {
console.log('newService: ' + newService);
});
expect(servicePage.getService()).toMatch('\bNo service\b');
});
The other thing that I'm seeing is that your pageObject appears to be a constructor when you could just use an object instead:
// name this file servicePage.js, and use as 'var servicePage = require('./servicePage.js');'
module.exports = {
changeServiceLink: element(by.id('serviceLink')),
service: element(by.id('service')),
clickChangeService: function() {
this.changeServiceLink.click();
},
getService: function() {
return this.service.getAttribute('value');
}
};
Otherwise you would have to do something like module.exports = new servicePage(); or instantiate it in your test file.
When you navigate another page, the web elements will be clear, that you selected. So you have to select again. You can select all elements that is in a page of HTML. You can click that you see. So the protactor + Selenium can decide what is displayed.
You have a mistake in your code, try this:
expect(servicePage.getService()).toMatch('\bNo service\b');
I'm new to SAPUI5
I'm trying to show the master page when a Tile is clicked in a home page. Below is the code that I have used with in event handler.
var context = evt.getSource().getBindingContext();
this.nav.to("Master", context);
The problem here is I am getting following error TypeError: this.nav.to is not a function
Please assist
What is this in your context? That depends on where your tilePressed method is defined. If it's defined in a controller, this will refer to the controller.
If it is defined statically - you often find this for formatter functions - this will refer to the control that triggered the event.
Is the variable this.nav defined and appropriatly initialized? It needs to contain some kind of NavContainer e.g. the outermost sap.m.App resp. sap.m.SplitApp.
Also check out this article on the usage of this in JavaScript and this very handy jQuery function jQuery.proxy
The this.nav.to() and this.nav.back() functions are not
defined in the App.controller.js. You need to write the following code
in App.controller.js so that the compiler identifies the functions
being called.
to : function (pageId, context) {
var app = this.getView().app;
// load page on demand
var master = ("Master" === pageId);
if (app.getPage(pageId, master) === null) {
var page = sap.ui.view({
id : pageId,
viewName : "ProjectPath.view." + pageId,
type : "XML"
});
page.getController().nav = this;
app.addPage(page, master);
jQuery.sap.log.info("app controller > loaded page: " + pageId);
}
// show the page
app.to(pageId);
// set data context on the page
if (context) {
var page = app.getPage(pageId);
page.setBindingContext(context);
}
},
back : function (pageId) {
this.getView().app.backToPage(pageId);
}
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.