skip to Main Content

“Check, please!” or Dynamic Properties in JSONModel

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:

Very common scenario in business application where we have a header object and items (sales order, PO, shopping cart and so on). Lets say we need to calculate a balance : balance = header amount – sum( item price * item quantity ) The most obvious way would be to implement a method in controller but the problem is – we have to assign a change event handler to every input field involved in calculation (dynamically or even worse – statically). Instead, we would define a dynamic property where we implement a calculation logic in the getter method. The beauty of this approach is that if the static property (amount or qty) from the target model is bound to the UI control, two way binding (default from JSONModel) will cause checkUpdate method execution and as a result – execution of dynamic property getter (of course only if it is bound to UI control’s property, like Text’s text) AND we have an access to object’s subobject, like items:

 


 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

Back To Top