SAPUI5反应式编程:ManagedObjectModel与JSONModel + Object.defineProperty

119 阅读3分钟

简介

这篇博客不会涉及像RxJs这样著名的传统反应式编程库。

但我建议你看一下这些博客。

这篇博客旨在强调如何在SAPUI5中简单地使用反应式编程,而不需要外部librairies或框架的开销。

我将介绍SAPUI5中原生反应式编程的两种方式。

  1. 使用ManagedObjectModelManagedObject()
  2. 使用简单的JSONModel+Object.defineProperty

我不会做任何比较,你必须根据使用情况调整使用其中一种。

请注意 ManagedObjectModel仍然在实验性API中

使用案例

在一个工厂里,我们确实有存储仓。在每个存储仓中,都存储着一组材料。我们想建立一个简单的SAPUI5应用程序,它应该根据可用的库存量来反映存储仓的状态。

规则1:如果一个材料的库存大于0,那么材料的状态应该是绿色的,否则是红色。

规则2:如果存储仓中的一种材料没有库存(库存等于0),那么存储仓状态应该是红色,否则是绿色。

规则3:存储仓和物料的状态应根据库存状态自动反映。

下面是我们的领域模型。

Domain%20Model

领域模型

使用ManagedObjectModel和ManagedObject

ManagedObject类引入了有趣的概念,允许对现实世界进行建模,如聚合和关联。

ManagedObjectModel结合起来,它提供了一个强大的使用属性设置器/获取器的反应式编程功能。

让我们从创建扩展了ManagedObject的类开始吧

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);
			}
		}
	});
});

每次股票变化时,我们将根据传递的值更新状态属性。这样,我们就不需要在应用逻辑中重新计算状态了。

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;
		}
	});
});

基于它的物料聚集状态,存储仓将动态地计算它的状态。

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 () {}
	});
});

不,我们将需要组装件来运行这个例子。

我们将使用一个静态数据。

{
    "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
                }
            ]
        }
    ]

}

建立ManagedObject对象(与它的聚合)和创建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");

在XLL视图中。

<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>

现在,如果你运行该应用程序,你将能够测试该功能。储存箱状态将根据其材料的库存可用性来反映。如果你使用步骤输入在材料层面改变库存状态,状态将自动反映。

Application%20%281%29

应用 (1)

使用简单的JSONModel + Object.defineProperty()

你可以通过简单地结合JSONModel和Object.defineProperty()实现同样的行为。正如我们在ManagedObject层面上通过定义setters/getters所做的那样,我们可以使用简单的 POJO (Plain Old Javascript Object)和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");

当你运行这个应用程序时,你会得到完全相同的行为。

结论

在开发具有丰富用户体验的商业应用时,我们需要考虑降低开发的复杂性。例如,反应式编程可以帮助你大大降低算法的复杂性,这对代码维护也有积极的影响。

也可以考虑在你的应用程序中使用基于事件的逻辑,将你的应用程序分割成不同的组件(关注点分离)并使用事件总线。构建对事件做出反应的组件有助于它们的整合,也有助于它们的退役。请看这篇关于如何正确使用EventBus的文章