SAPUI5 Reactive Programming: ManagedObjectModel vs JSONModel + Object.defineProperty

This blog won’t deal with well known and traditional reactive programming libraries like RxJs.

But I advise you to have a look at those blogs:

This blog aims to underline how to simply use reactive programming in SAPUI5 with now overhead of external librairies or frameworks.

I am introducing 2 ways of native reactive programming in SAPUI5:

  1. Using ManagedObjectModel and ManagedObject()
  2. Using simple JSONModel + Object.defineProperty

I won’t make any comparaison and you will have adapt the use one or the other according to use case.

Please pay attention to that the ManagedObjectModel still in Experimental API

At a Plant we do have Storage Bins. In each Storage Bin it is stored a set of Materials. We would like to build a simple SAPUI5 application that should reflect the status of the Storage Bins according to stock quantity available.

Rule#1: If a material stock greater than 0, then material status should be GREEN , else RED.

Rule#2: If one of the materials in a Storage Bin is our of stock (Stock equals to 0), then Storage Bin Status should RED, else GREEN

Rule#3: The status of Storage Bins and Materials should be reflected automatically according to the stock status.

Here is our domain model:

Domain%20Model

Domain Model

The ManagedObject class introduces interesting concepts allowing to model the real world like aggregations and associations.

Combined with ManagedObjectModel it provides a powerful reactive programing features using properties setters/getters.

Let’s start by creating our classes that extends the ManagedObject

Classes

Classes

Material.js

sap.ui.define([ "sap/ui/base/ManagedObject"
], /** * @param {typeof sap.ui.base.ManagedObject} ManagedObect */
function (ManagedObect) { "use strict"; return ManagedObect.extend("sample.ui5.reactive.bean.Material", { metadata: { properties: { uid: "string", name: "string", price: "int", stock: "int", status: "boolean" }, aggregations: {}, events: {} }, init: function () {}, setStock: function (value) { this.setProperty("stock", value, true); if (value && value > 0) { this.setProperty("status", true); } else { this.setProperty("status", false); } } });
});

Each time the stock change, we will update the status property according to the passed value. Like this we don’t have to matter about recalculating the status in the application logic.

StorageBin.js

sap.ui.define([ "sap/ui/base/ManagedObject"
], /** * @param {typeof sap.ui.base.ManagedObject} ManagedObect */
function (ManagedObect) { "use strict"; return ManagedObect.extend("sample.ui5.reactive.bean.StorageBin", { metadata: { properties: { name: "string", status:"boolean" }, aggregations: { materials:{ type:"sample.ui5.reactive.bean.Material", multiple: true } }, events: {} }, init: function () {}, getStatus:function(){ var aMaterials = this.getAggregation("materials"); var bStockZero = true; $.each(aMaterials, function(index, item){ if(item.getStock() === 0){ bStockZero = false; ///exit return false; } }); return bStockZero; } });
});

Basic on its materials aggregation status, the Storage Bin will calculate dynamically its status.

Warehouse.js

sap.ui.define([ "sap/ui/base/ManagedObject"
], /** * @param {typeof sap.ui.base.ManagedObject} ManagedObect */
function (ManagedObect) { "use strict"; return ManagedObect.extend("sample.ui5.reactive.bean.Warehouse", { metadata: { properties: { name: "string" }, aggregations: { storageBins:{ type:"sample.ui5.reactive.bean.StorageBin", multiple: true } }, events: {} }, init: function () {} });
});

No we will need to assemble pieces to run the example.

We will use a static data:

{ "storageBins":[ { "name":"Storage Bin 1", "materials":[ { "uuid":"11000211", "name":"Material 1", "price":100, "stock":0 }, { "uuid":"11000212", "name":"Material 2", "price":200, "stock":5 } ] }, { "name":"Storage Bin 2", "materials":[ { "uuid":"11000211", "name":"Material 3", "price":45, "stock":8 }, { "uuid":"11000212", "name":"Material 4", "price":201570, "stock":41 } ] } ] }

Build the ManagedObject Object (with its aggregations) and Create the ManagedObjectModel:

var oDataModelJson = this.getOwnerComponent().getModel("DataModelJson");
var oData = oDataModelJson.getData(); var oWarehouse = new Warehouse(); $.each(oData.storageBins, function(index, item){ var oStorageBin = new StorageBin(); oStorageBin.setName(item.name); $.each(item.materials, function(indexM, itemM){ var oMaterial = new Material(); oMaterial.setUid(itemM.uuid); oMaterial.setName(itemM.name); oMaterial.setPrice(itemM.price); oMaterial.setStock(itemM.stock); oStorageBin.addMaterial(oMaterial); }); oWarehouse.addStorageBin(oStorageBin);
}); this.getView().setModel(new ManagedObjectModel(oWarehouse), "DataModel");

In the XLL View:

<View controllerName="sample.ui5.reactive.controller.Main" xmlns:mvc="sap.ui.core.mvc" displayBlock="true" xmlns="sap.m"> <Page id="page" title="{i18n>title}"> <content> <Table id="idProductsTable" inset="false" items="{ path: 'DataModel>/storageBins' }" mode="SingleSelectMaster" itemPress=".onItemPress"> <columns> <Column width="12em"> <Text text="Storage Bin" /> </Column> <Column minScreenWidth="Desktop" demandPopin="true" hAlign="End"> <Text text="Status" /> </Column> </columns> <items> <ColumnListItem vAlign="Middle" type="Active" highlight="{= ${DataModel>status}?'Success':'Error'}"> <cells> <ObjectIdentifier title="{DataModel>name}" text="{DataModel>uid}" /> <Text text="{DataModel>status}" /> </cells> </ColumnListItem> </items> </Table> <HBox height="100px" ></HBox> <Table id="idMaterialsTable" inset="false" items="{ path: 'DataModel>materials' }"> <columns> <Column width="12em"> <Text text="Material" /> </Column> <Column minScreenWidth="Desktop" demandPopin="true" hAlign="End"> <Text text="Status" /> </Column> <Column minScreenWidth="Desktop" demandPopin="true" hAlign="End"> <Text text="Stock" /> </Column> </columns> <items> <ColumnListItem vAlign="Middle" highlight="{= ${DataModel>status}?'Success':'Error'}"> <cells> <ObjectIdentifier title="{DataModel>name}" text="{DataModel>uid}" /> <Text text="{DataModel>status}" /> <StepInput value="{DataModel>stock}" min="0" max="1000" width="120px" step="1"/> </cells> </ColumnListItem> </items> </Table> </content> </Page>
</View>

Now if you run the application you will be able to test the feature: The Storage Bin Status will be reflected according to the stock availability of its materials. If you change the stock status at the Materials level using the Step Input, the status will be reflected automatically.

Application%20%281%29

Application (1)

You could achieve the same behavior by simply combining JSONModel and Object.defineProperty(). As we did at the ManagedObject level by defining setters/getters we could accomplish the same using simple POJO (Plain Old Javascript Object) and Object.defineProperty().

var oDataModelJson = this.getOwnerComponent().getModel("DataModelJson"); var oData = oDataModelJson.getData(); $.each(oData.storageBins, function(index, item){ Object.defineProperty(item, "status", { get: function () { var bStockZero = true; $.each(item.materials, function(indexM, itemM){ if(itemM.stock === 0){ bStockZero = false; ///exit return false; } }); return bStockZero; } }); $.each(item.materials, function(indexM, itemM){ Object.defineProperty(itemM, "status", { get: function () { return !!(itemM.stock && itemM.stock > 0); } }); });
}); this.getView().setModel(oDataModelJson, "DataModel");

When you run the application you will get exactly the same behavior.

When developing Business Application with a rich UX we need to think about reducing the complexity of development. The Reactive Programming for instance helps you reducing considerably the complexity of your algorithms which will have also a positive impact on the code maintenance.

Think also about using Event Based logic in your application by splitting your application on separate components (Separation of Concerns) and the use of EventBus. Building components reacting on events facilitate their integration and also their decommissioning. Please have a look at this article on how to use correctly the EventBus.

You could find the code here under github. Please clone it and give the both alternative a try and provide comments and feedback.