Extend sap.m.Input: onAfterRendering method doesn't work - sapui5

I created a custom extension for sap.m.Input. In onAfterRendering I want to mask the value using using jquery-maskmoney as follows:
$('#'+this.getId()+'-inner').maskMoney({ thousands : '', decimal : '.' });'
When I apply mask in the console, everything works fine. But when I try to
add it in the onAfterRendering method, I get some errors when I am trying to setValue:
amountInputControl.setValue(data.amount); // Its is an instance of NumericInput
Error:
TypeError: Cannot read property 'val' of undefined
at sap.m.InputBase._getInputValue (InputBase.js:9)
at sap.m.InputBase.updateDomValue (InputBase.js:32)
at sap.m.InputBase.setValue (InputBase.js:34)
at sap.ui.controller.updateFieldsForReference //Here was executed operation setValue
NumericInput.js
jQuery.sap.declare("control.customInputTypes.NumericInput");
sap.ui.define(['jquery.sap.global', 'sap/m/Input'],
function(jQuery, BaseInput) {
"use strict";
var commonControlInput = BaseInput.extend('control.customInputTypes.NumericInput', /** #lends sap.m.Input.prototype */ {
metadata: {},
renderer : {
render : function(oRm, oControl) {
sap.m.InputRenderer.render(oRm, oControl);
}
}
});
commonControlInput.prototype.onAfterRendering = function () {
$('#'+this.getId()+'-inner').maskMoney({ thousands : '', decimal : '.' });
};
return commonControlInput;
}, /* bExport= */ true);
I didn't even touch the InputBase class, so I wonder whats wrong? If I don't apply this mask everything works fine.
Maybe I cannot use jQuery in the onAfterRendering method of a control?

At first I tought you might want to check sap.m.MaskInput, but I guess that's not exactly what you want...
Anyway, there are a few things I would change in your code. Here is a running jsbin example:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>SAPUI5 single file template | nabisoft</title>
<script src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js"
id="sap-ui-bootstrap"
data-sap-ui-theme="sap_bluecrystal"
data-sap-ui-libs="sap.m"
data-sap-ui-bindingSyntax="complex"
data-sap-ui-compatVersion="edge"
data-sap-ui-preload="async"></script>
<!-- use "sync" or change the code below if you have issues -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-maskmoney/3.0.2/jquery.maskMoney.min.js"></script>
<!-- XMLView -->
<script id="myXmlView" type="ui5/xmlview">
<mvc:View
controllerName="MyController"
xmlns="sap.m"
xmlns:core="sap.ui.core"
xmlns:mvc="sap.ui.core.mvc"
xmlns:cit="control.customInputTypes">
<cit:NumericInput value="1219999234" />
</mvc:View>
</script>
<script>
sap.ui.getCore().attachInit(function () {
"use strict";
//### Custom Control ###
// remove the first parameter in "real" apps
sap.ui.define("control/customInputTypes/NumericInput",[
"jquery.sap.global",
"sap/m/Input",
"sap/m/InputRenderer"
], function(jQuery, Input, InputRenderer) {
"use strict";
return Input.extend("control.customInputTypes.NumericInput", {
init : function () {
this.addEventDelegate({
onAfterRendering : function(){
var $input = jQuery("#"+this.getId()+"-inner");
$input.maskMoney({
thousands : ".",
decimal : ","
}).maskMoney("mask");
}.bind(this)
});
},
renderer : InputRenderer
});
});
//### Controller ###
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function (Controller) {
"use strict";
return Controller.extend("MyController", {
onInit : function () {
}
});
});
//### THE APP: place the XMLView somewhere into DOM ###
sap.ui.xmlview({
viewContent : jQuery("#myXmlView").html()
}).placeAt("content");
});
</script>
</head>
<body class="sapUiBody">
<div id="content"></div>
</body>
</html>

Related

Image not displaying in SAP UI5 Webpage

I am new to SAP UI5 and I am trying to develop a webpage with images and instructions. I am unable to display an image in my Webapp and need help with it. I looked through the internet and could not find any proper solution yet. Below is my code. Can you please help me understand what I am missing?
index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My App</title>
<script
id="sap-ui-bootstrap"
src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-theme="sap_belize"
data-sap-ui-libs="sap.m"
data-sap-ui-compatVersion="edge"
data-sap-ui-async="true"
data-sap-ui-onInit="module:sap/ui/demo/walkthrough/index"
data-sap-ui-resourceroots='{
"sap.ui.demo.walkthrough": "./"
}'>
</script>
<style>
.myimage{float:right !important; width:300px; height:200px;}
</style>
</head>
<body class="sapUiBody" id="content">
</body>
</html>
App.view.xml:
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.App"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
>
<Page title="Image">
<content>
<Image id="QRCode" src="images/img1.jpg" />
</content>
</Page>
</mvc:View>
App.controller.js:
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function(
Controller
) {
"use strict";
return Controller.extend("sap.ui.demo.walkthrough.controller.App", {
onBeforeRendering: function() {
this.getView().byId("QRCode").addStyleClass("myimage");
},
});
});
index.js:
sap.ui.define([
"sap/ui/core/mvc/XMLView"
], function (XMLView) {
"use strict";
XMLView.create({
viewName: "sap.ui.demo.walkthrough.view.App"
}).then(function (oView) {
oView.placeAt("content");
});
});
Loading images is a little tricky, especially if you want it to run in the launchpad.
You should create the path dynamically in your controller and put it into a view model:
const sPath = sap.ui.require.toUrl("sap/ui/demo/walkthrough/images/img1.png");
const oModel = new JSONModel({ imagePath: sPath });
this.getView().setModel(oModel, "view");
<Image id="QRCode" src="{view>/imagePath}" />
Of course it's very important to pass the correct namespace to sap.ui.require.toUrl

Why UI5 controls rendered inside a custom control are not rerendered on property change?

I have created a custom control my.Control that renders a sap.m.Text directly and receives another one by an aggregation.
Paste an example in one file for simplicity:
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
<title>SAPUI5 example</title>
<script id="sap-ui-bootstrap"
src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-theme="sap_belize"
data-sap-ui-libs="sap.m, sap.ui.core"
data-sap-ui-compatVersion="edge"
data-sap-ui-async="true">
</script>
<script>
sap.ui.getCore().attachInit(function () {
sap.ui.core.Control.extend("my.Control", {
metadata: {
aggregations: {
testControl: {
type: 'sap.m.Text',
multiple: false,
singularName: 'testControl'
}
}
},
renderer: function (oRm, oControl) {
oRm.openStart('div', oControl);
oRm.openEnd();
oRm.text('DIRECTLY GENERATED CONTROL : ');
oRm.renderControl(new sap.m.Text('direct-control', {text: 'initial value'}));
oRm.openStart('br');
oRm.openEnd();
oRm.text('AGGREGATION PASSED CONTROL : ');
oRm.renderControl(oControl.getTestControl());
oRm.close('div');
}
});
new my.Control({
testControl: [
new sap.m.Text('passed-control', {text: 'initial value'})
]
}).placeAt('content');
});
</script>
</head>
<body class="sapuiBody" id="content">
</body>
</html>
Both load OK. But when editing its text property via Javascript the passed control is updated and the other one is not:
sap.ui.getCore().byId('passed-control').setText('edited value')
Control is rerendered
sap.ui.getCore().byId('direct-control').setText('edited value')
Control is not rerendered
If I execute sap.ui.getCore().byId('direct-control').rerender() then "direct-control" is rerendered with "edited value" as text.
Why do they behave differently?
Is there a way to configure this behaviour?
Thanks.

Is it possible to add Binding change event to a component in XML view?

I have a component in SAPUI5 where I need to listen to changes in the binding (Binding.change event). At the moment I'm adding a listener to the Binding in modelContextChange like this:
function onModelContextChange(oEvent){
var oSelect = oEvent.getSource();
var binding = oSelect.getBinding("items");
if(binding){
binding.attachChange(onDataChange, oSelect);
}
}
However this causes all kinds of weird problems, because modelContextChange could be fired multiple times. It would be better to to this in the XML view. I've tried code like this, but it doesn't work.
<Select items="{ path: 'project>/data/', change:'onDataChange' templateShareable: false">
<core:Item key="{project>Id}" text="{project>Parameter}"/>
</Select>
Any recommendations how to do this?
I just came across the problem, too. I solved it in that way:
<Select items="{
path: 'project>/data',
events: {
change: '.onDataChange'
},
templateShareable: false
}">
You can pass an events object to the binding and use the available bindings (e.g. v2.ODataListBinding). Note that you have to use a dot before your function name (.onDataChange). In your controller you can add the function:
onDataChange: function(oEvent) {
// Your implementation...
},
If I remember well from the JS Views, I think it is like this:
<Select items="{ path: 'project>/data/', events: {change:'onDataChange'}, templateShareable: false}">
This is for listening to the Model "change" events.
If you want to listen to the "change" event in the Select control, this is when the user selects a different value in the dropdown, it is like this:
<Select items="{ path: 'project>/data/', templateShareable: false}" change="onDataChange">
EDIT:
Using "modelContextChange" event.
<Select items="{ path: 'project>/data/', templateShareable: false}" modelContextChange="onDataChange">
here is an example
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
<script
src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js"
id="sap-ui-bootstrap"
data-sap-ui-theme="sap_bluecrystal"
data-sap-ui-xx-bindingSyntax="complex"
data-sap-ui-libs="sap.m"></script>
<style type="text/css">
.sapMObjLTitle {
cursor: pointer;
}
</style>
<!-- XML-based view definition -->
<script id="oView" type="sapui5/xmlview">
<mvc:View height="100%" controllerName="myView.Template"
xmlns="sap.m"
xmlns:core="sap.ui.core"
xmlns:mvc="sap.ui.core.mvc">
<Select change="onDataChange" items="{ path: 'project>/data', templateShareable: false}">
<core:Item key="{project>Id}" text="{project>Parameter}"/>
</Select>
</mvc:View>
</script>
</head>
<body class="sapUiBody">
<div id='content'></div>
</body>
</html>
sap.ui.define([
'jquery.sap.global',
'sap/ui/core/mvc/Controller',
'sap/ui/model/json/JSONModel'
], function(jQuery, Controller, JSONModel) {
var ListController = Controller.extend("myView.Template", {
onInit: function(oEvent) {
var oView = this.getView();
oView.setModel(new JSONModel({
data: [{
Id: "1",
Parameter: "One"
}, {
Id: "2",
Parameter: "Two"
}]
}), "project");
},
onDataChange: function() {
alert("changed")
}
});
return ListController;
});
// Instantiate the View and display
var oView = sap.ui.xmlview({
viewContent: jQuery('#oView').html()
});
oView.placeAt('content');
https://jsbin.com/zufurav/edit?html,js,output
Note: the change attribute in your XML is incorrect
I managed to get the binding change event with this set to the element that I needed by attaching a modelContextChange to the element and handling attaching the change event to the binding in there. Here's the code from the view controller:
modelContextChange: function(oEvent) {
var oElement = oEvent.getSource();
var binding = oElement.getBinding("items");
if (binding) {
//Binding change event could already be attached, detach it first, if it's there
binding.detachChange(this.onBindingChange, oSelect);
binding.attachChange(this.onBindingChange, oSelect);
// Beacause I used this inside a sap.ui.table.Treetable,
// in some cases binding changes occur without the binding change event firing.
// Manually fire the binding change just in case
// YMMV
binding.refresh(true);
}
},
onBindingChange: function() {
//The code that needs to run at binding change
//"this" is set to the correct element
//I specifically needed to add one element in front of other items in a sap.m.Select
var items = this.removeAllItems();
this.addItem(new sap.ui.core.Item({
key: 0,
text: "< Inherit >"
}));
items.forEach(function(item) {
this.addItem(item);
}, this);
}

Dynamic link in MessagePopover fragment not showing

I've been developing a UI5 app that uses a model to store notifications. I want to display these notifications inside a MessagePopover that can be triggered using the footer.
The data binding works perfectly. I can see that all the properties are set and match the data inside the model. But the link in the details page is not showing up.
When I use a static Link (i.e. a static link to google.com) that is not using data binding, the link is rendered.
Does anyone of you came across the same problem and has a solution for it?
Here is my code:
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function(BaseController) {
return BaseController.extend("test.Test", {
onShowNotificationPopover: function(oEvent){
if (!this._notificationPopover) {
var oMessageTemplate = new sap.m.MessageItem({
type : '{data>type}',
title : '{data>title}',
subtitle : "{data>subtitle}",
description : '{data>description}',
markupDescription : "{data>markupDescription}",
groupName : "{data>groupName}"
});
var oLink = new sap.m.Link({
text : "{data>link/text}",
href : "{data>link/url}",
target : "_blank"
});
/* Working with static */
// oLink = new sap.m.Link({text: "Google", href: "http://google.com", target: "_blank"})
oMessageTemplate.setLink(oLink);
var oPopover = new sap.m.MessagePopover();
oPopover.bindAggregation("items",{
template: oMessageTemplate,
path: "data>/notifications"
});
this._notificationPopover = oPopover;
this.getView().addDependent(this._notificationPopover);
}
this._notificationPopover.toggle(oEvent.getSource());
}
});
});
The view contains the following:
<mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns:f="sap.f" xmlns="sap.m" controllerName="test.Test">
<f:DynamicPage showFooter="true">
<f:footer>
<OverflowToolbar>
<Button icon="sap-icon://message-popup" type="Emphasized"
press="onShowNotificationPopover" />
</OverflowToolbar>
</f:footer>
</f:DynamicPage>
</mvc:View>
And the index.html
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv='Content-Type' content='text/html;charset=UTF-8'/>
<script src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js"
id="sap-ui-bootstrap"
data-sap-ui-libs="sap.m, sap.f"
data-sap-ui-theme="sap_belize"
data-sap-ui-bindingSyntax="complex"
data-sap-ui-resourceroots='{
"test": "."
}'
>
</script>
<script>
sap.ui.getCore().attachInit(function() {
var oModel = new sap.ui.model.json.JSONModel();
oModel.setData({
notifications: [{
type: "Error",
title: "Title1",
subtitle: "Subtitle",
description: "This is a description and below you should see a link",
markupDescription: false,
groupName: "notification",
link: {
text: "Click here",
url: "http://www.google.com"
}
}]
});
var oView = sap.ui.xmlview("test.Test");
oView.setModel(oModel, "data");
oView.placeAt("content");
});
</script>
</head>
<body class="sapUiBody" role="application">
<div id="content"></div>
</body>
</html>
Example: Popover with dynamic link
Example: Popover with static link
I found a solution for my problem: the event itemPress of MessagePopover.
The event handler inside the controller:
onNotificationItemSelect: function(oEvent){
var oItem = oEvent.getParameter("item"), oBindingContext = oItem.getBindingContext("data");
var oData = oBindingContext.getModel().getProperty(oBindingContext.getPath());
oItem.getLink().setText(oData.link.text);
oItem.getLink().setHref(oData.link.url);
}
And you have to register it:
var oPopover = new sap.m.MessagePopover({
itemSelect: this.onNotificationItemSelect
});
Additional Information
When you display the content of the popover using
console.log(oPopover.getItems()[0].getLink())
the correct properties are shown. see here
But when you search the DOM you can see that the link is copied and does not contain the required binding. see here

How to use if else in text property to display either one value or another?

Having two distinct values such as QtValidF and DocDate, I want to be able to display DocDate only if QtValidF comes null. How could I condition this?
<Text text="{
path: 'QtValidF',
formatter:'.formatoFecha'
}"/>
I'm trying this but doesn't work.
<Text text="{= ${QtValidF} != 'null' ? {
path: 'DocDate',
formatter: '.formatoFecha'
} : {
path: 'QtValidF',
formatter: '.formatoFecha'
}}"/>
Expression Binding
In general, you can make use of expression binding for a short and simple conditional expression.
<MyControl anyProperty="{= ${myPrimary} || ${mySecondary}}" />
Just like in JS, when the first operand results in one of the falsy values, the second one is taken into account.
Formatter
In case a formatter is preferred, the composite binding syntax can be used:
<MyControl anyProperty="{
parts: [
'myPrimary',
'mySecondary'
],
formatter: '.getThatValue'
}" />
Then in the controller:
getThatValue: function(primaryValue, secondaryValue) {
return primaryValue || secondaryValue;
},
For more information, please take a look at the topics Binding Syntax and Composite Binding.
Here is the inline binding expression:
value='{= ${/QValidF} !== null ? ${/QValidF} : ${/DocDate} }'
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>Bernard LeTourneur - UI5 Single Page</title>
<script
src='https://sapui5.hana.ondemand.com/resources/sap-ui-core.js'
id='sap-ui-bootstrap'
data-sap-ui-theme='sap_bluecrystal'
data-sap-ui-libs='sap.m'
data-sap-ui-bindingSyntax='complex'
data-sap-ui-compatVersion='edge'
data-sap-ui-preload='async'>
</script>
<script id='myXmlView' type='ui5/xmlview'>
<mvc:View
controllerName='MyController'
xmlns='sap.m'
xmlns:core='sap.ui.core'
xmlns:mvc='sap.ui.core.mvc'>
<Input id='inputStatus' enabled='false' value='{= ${/QValidF} !== null ? ${/QValidF} : ${/DocDate} }' />
<Button press="handleDataChange" text='{= ${/QValidF} !== null ? "Set QValidF to Null" : "Set QValidF to something"}' />
</mvc:View>
</script>
<script>
sap.ui.getCore().attachInit(function () {
'use strict';
sap.ui.define([
'sap/ui/core/mvc/Controller',
'sap/ui/model/json/JSONModel'
], function (Controller, JSONModel) {
'use strict';
return Controller.extend('MyController', {
onInit : function () {
var that = this;
var data = {QValidF: "something", DocDate: "2018/06/08"};
var oModel = new JSONModel(data);
this.getView().setModel(oModel);
},
handleDataChange: function(){
var isNull = this.getView().getModel().getProperty("/QValidF");
console.log(isNull);
if (isNull=="something"){
this.getView().getModel().setProperty("/", {QValidF: null, DocDate: "2018/06/08"});
}
else{
this.getView().getModel().setProperty("/", {QValidF: "something", DocDate: "2018/06/08"});
}
}
});
});
sap.ui.xmlview({
viewContent : jQuery('#myXmlView').html()
}).placeAt('content');
});
</script>
</head>
<body class='sapUiBody'>
<div id='content'></div>
</body>
</html>