WebDynpro Java has a nice feature called calculated context attributes which I missed a lot in WDA and now in SAP(Open)UI5. Very often we have to perform a data manipulation on the UI side to produce “derivatives” – data not directly available from the data source but required to the end user. Another example where calculation on the fly is required is the calculation driven by user input,
Wait, there is the concept of formatter or expression binding in SAPUI5, you might say! Yes, and they work perfectly fine for simple data manipulation within model. By simple I mean – every part of the calculation should be explicitly declared and only attributes could be used, like example from
Here
oTxt.bindValue({ parts: [ {path: "/firstName", type: new sap.ui.model.type.String()}, {path: "/lastName", type: new sap.ui.model.type.String()}, {path: "/amount", type: new sap.ui.model.type.Float()}, {path: "/currency", type: new sap.ui.model.type.String()} ], formatter: function(firstName, lastName, amount, currency){ // all parameters are strings if (firstName && lastName) { return "Dear " + firstName + " " + lastName + ". Your current balance is: " + amount + " " + currency; } else { return null; } } });
all 4 parameters must be declared even though they all are coming from the same context. An by within the model means you have to define at least one part for the formatter even that part is not needed.
Those limitations are relatively easy to overcome by dynamic properties declared in JSONModel. What is a dynamic property? It is the property defined by calling Object.defineProperty ( Object.defineProperty() – JavaScript | MDN ).
Why do I call them dynamic? Because you can define getter and setter functions! JavaScript’s functional nature meets JavaBeans.
Example 1:
From version 1.28 there is a control sap.m.MessagePopover is available and recommended to use by Fiori Design Guidelines ( https://experience.sap.com/fiori-design/ui-components/message-popover/ ). That control is usually “opened by” some other control, mostly by buttons. So, if there are no active messages in our message manager we would hide that button to prevent users’ confusion about errors. Another nice addition to that would be – the display icon which corresponds to the highest severity in the message manager e.g. if there are errors – error icon, if there are just warnings – warning icon, and so on. To implement those requirements we would define 2 dynamic properties messagePopoverVisible and messagePopoverButonIcon:
var messagePopoverModelObject = {}; Object.defineProperty(messagePopoverModelObject, "messagePopoverVisible", { get: function() { var data = sap.ui.getCore().getMessageManager().getMessageModel().getData(); return data && data.length !== 0; } }); Object.defineProperty(messagePopoverModelObject, "messagePopoverButonIcon", { get: function() { var data = sap.ui.getCore().getMessageManager().getMessageModel().getData(); var hasSeverity = function(severity) { return function(element, index, array) { return (element.type == severity); }; }; if (data && jQuery.isArray(data)) { if (data.some(hasSeverity("Error"))) { return "sap-icon://alert"; } if (data.some(hasSeverity("Warning"))) { return "sap-icon://alert"; } } return "sap-icon://sys-enter"; } }); this.getView().setModel(new JSONModel(messagePopoverModelObject), "messagePopoverModel");
now with those properties defined, we can use them in the binding:
<Button visible="{messagePopover>/messagePopoverVisible}" icon="{messagePopover>/messagePopoverButonIcon}"/>
Note: MessageManage model could be set for the button control and visibility could be bound via expression binding: “{=$(messageManagerModel>/).length!=0}” but I personally prefer those kinds of validation to have in JavaScript – easier to debug and understand.
Example 2:
ABAP has no boolean type, so if we need to map a true value to ‘X’ and vice versa. We can use a dynamic property as it has both, setter and getter (another way to implement – Simple type implementation with formatValue and parseValue).
$.each([ "DisabilityIndicator", "StudentIndicator", "MedicareIndicator"], function(i, entityName) { Object.defineProperty(dataObject, entityName + "Boolean", { get : function() { return (this[entityName] === "X"); }, set : function(value) { this[entityName] = (value ? "X" : " "); } }); });
This example ( from ESS Dependents application) creates dynamically a corresponding value holder which could be used for mapping:
<CheckBox selected="{DisabilityIndicatorBoolean}"/>
Example 3:
Object.defineProperty(itemsModel, "Balance", { get: function () { var balance = 0.00; var itemsAmount = 0.00; $.each(this.Items, function (i, item) { if (!isNaN(parseFloat(item.Amount)) && !isNaN(parseFloat(item.Qty))) { itemsAmount = itemsAmount + parseFloat(item.Amount) * parseFloat(item.Qty); } }); balance = this.header.Amount - itemsAmount; return balance; } });
and of course as it is a model property we can use it in binding:
<Toolbar id="BalanceBar"> <content> <ToolbarSpacer/> <Label text="{i18n>BALANCE}"></Label> <ToolbarSpacer width="20px"/> <Text text="{ parts : [ 'itemsModel>/Balance' ], formatter: '.formatAmount' }"></Text> </content> </Toolbar>
Limitation – If the dynamic property is declared on the object which should be used in arrays (tables, lists, etc.) some sort of “cloning” logic should be implemented.
checkUpdate() should be called if data is changed indirectly and affects the calculation logic.
Disclaimer: Those techniques obviously not the only ones available to implement the above-mentioned requirements but just give the developer more options to consider.
To learn more about this solution, or find an answer to your nagging issue, contact Mindset here.
If you are interested in viewing similar articles, visit our blog, here.
View our LinkedIn, here.