赞
踩
我们在Visual Studio Code 里通过 Guided Procedure,可以给 Fiori Elements 框架生成的 List Report 里的 Table,添加自定义按钮,如下图 Jerry 的按钮
所示。
但实际工作中,有朋友反映,在 Fiori Elements 的 Guided Procedure 中通过向导,一路 Next Next,对于开发人员来说就是个黑盒子。虽然实现了需求,但不知道背后是怎么工作的,觉得一切很不踏实。
本文就来深入介绍 Fiori Elements 里 Smart Table 控件的工作原理。
我们知道 Fiori Elements List Report 的模板,包含了 SmartTable.fragment.xml 这个页面片段:
而该页面片段的源代码里,使用了 Smart Table 控件:
XML 视图的完整源代码:
<mvc:View xmlns=".m" xmlns:mvc=".ui.core.mvc" controllerName=".ui.demo.smartControls.SmartTable" xmlns:smartTable=".ui.comp.smarttable"> <smartTable:SmartTable id="smartTable_ResponsiveTable" tableType="ResponsiveTable" editable="false" entitySet="Products" header="Jerry的产品" showRowCount="true" useExportToExcel="false" enableAutoBinding="true"> </smartTable:SmartTable> </mvc:View>
XML 视图里定义了一个 Smart Table 控件,第 10 行代码 entitySet=“Products”, 意思是让该控件,在运行时"智能地" 将名称为 Products 的 OData 模型里,所有符合某种条件的字段,渲染成表格列项目。
这个包含了 Smart Table 控件的 UI5 应用,最终渲染成包含如下三列的表格:产品 ID,价格 (含金额和货币单位) 以及产品名称。
我们打开 metadata.xml, 找到了 Product 模型包含的四个属性字段。这四个属性字段,都作为最后渲染出的列项目的备选字段。其中 Price 字段,通过属性 :unit, 和 CurrencyCode 字段关联起来,作为同一个表格备选列项目。
metadata.xml 的源代码:
<?xml version="1.0" encoding="utf-8"?> <edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:="http://www..com/Protocols/Data"> <edmx:DataServices m:DataServiceVersion="2.0"> <Schema Namespace="com..wt05" :schema-version="1" xmlns="http://schemas.microsoft.com/ado/2008/09/edm"> <EntityType Name="Product"> <Key> <PropertyRef Name="ProductId" /> </Key> <Property Name="ProductId" Type="Edm.String" :label="Jerry产品ID" :filterable="false" /> <Property Name="Name" Type="Edm.String" MaxLength="30" :label="Name" :filterable="false" /> <Property Name="Category" Type="Edm.String" :label="Category" :filterable="true" /> <Property Name="Price" Type="Edm.String" :unit="CurrencyCode" MaxLength="3" :label="Price" :filterable="false" /> <Property Name="CurrencyCode" Type="Edm.String" MaxLength="3" :label="Currency" :semantics="currency-code" :filterable="true" /> </EntityType> <EntityType Name="Currency"> <Key> <PropertyRef Name="CURR" /> </Key> <Property Name="CURR" Type="Edm.String" MaxLength="4" :display-format="UpperCase" :text="DESCR" :label="Currency Code" :filterable="false" /> <Property Name="DESCR" Type="Edm.String" MaxLength="25" :label="Description" /> </EntityType> <EntityType Name="Category"> <Key> <PropertyRef Name="CAT" /> </Key> <Property Name="CAT" Type="Edm.String" MaxLength="4" :display-format="UpperCase" :text="DESCR" :label="Category" :filterable="false" /> <Property Name="DESCR" Type="Edm.String" MaxLength="25" :label="Description" /> </EntityType> <EntityContainer m:IsDefaultEntityContainer="true" :supported-formats="atom json"> <EntitySet Name="Products" EntityType="com..wt05.Product" /> <EntitySet Name="Currency" EntityType="com..wt05.Currency" /> <EntitySet Name="Category" EntityType="com..wt05.Category" /> </EntityContainer> <Annotations Target="com..wt05.Product/CurrencyCode" xmlns="http://docs.oasis-open.org/odata/ns/edm"> <Annotation Term="com..vocabularies.Common.v1.ValueList"> <Record> <PropertyValue Property="Label" String="Currency" /> <PropertyValue Property="CollectionPath" String="Currency" /> <PropertyValue Property="SearchSupported" Bool="true" /> <PropertyValue Property="Parameters"> <Collection> <Record Type="com..vocabularies.Common.v1.ValueListParameterOut"> <PropertyValue Property="LocalDataProperty" PropertyPath="CurrencyCode" /> <PropertyValue Property="ValueListProperty" String="CURR" /> </Record> <Record Type="com..vocabularies.Common.v1.ValueListParameterDisplayOnly"> <PropertyValue Property="ValueListProperty" String="DESCR" /> </Record> </Collection> </PropertyValue> </Record> </Annotation> </Annotations> <Annotations Target="com..wt05.Product/Category" xmlns="http://docs.oasis-open.org/odata/ns/edm"> <Annotation Term="com..vocabularies.Common.v1.ValueList"> <Record> <PropertyValue Property="Label" String="Category" /> <PropertyValue Property="CollectionPath" String="Category" /> <PropertyValue Property="SearchSupported" Bool="true" /> <PropertyValue Property="Parameters"> <Collection> <Record Type="com..vocabularies.Common.v1.ValueListParameterOut"> <PropertyValue Property="LocalDataProperty" PropertyPath="Category" /> <PropertyValue Property="ValueListProperty" String="CAT" /> </Record> <Record Type="com..vocabularies.Common.v1.ValueListParameterDisplayOnly"> <PropertyValue Property="ValueListProperty" String="DESCR" /> </Record> </Collection> </PropertyValue> </Record> </Annotation> </Annotations> <Annotations Target="com..wt05.Product" xmlns="http://docs.oasis-open.org/odata/ns/edm"> <Annotation Term="com..vocabularies.UI.v1.LineItem"> <Collection> <Record Type="com..vocabularies.UI.v1.DataField"> <PropertyValue Property="Value" Path="Name" /> </Record> <Record Type="com..vocabularies.UI.v1.DataField"> <PropertyValue Property="Value" Path="ProductId" /> </Record> </Collection> </Annotation> </Annotations> </Schema> </edmx:DataServices> </edmx:Edmx>
尽管 Product 模型包含了 4 个字段作为表格备选列项目,但为什么最终渲染出的页面里,我们只看到了 3 个行项目?名为 Category 的字段为什么没能渲染成行项目?
答案在 metadata.xml 的注解区域。
帮助文档提到,其所属的 OData 模型被注解 com…vocabiularies.UI.LineItem 修饰,且类型为 com…vocabularies.UI.DataField 的字段,在运行时会被 UI5 框架绘制成表格列项目。
为了验证这个结论,我们对 metadata.xml 里的元数据进行一些修改。比如现在只定义两个表格列项目,分别为ProductId 和 Name. 同时,我用 :label, 给属性 ProductId 分配标签为 “Jerry产品ID”:
运行时的效果:Name 列表项出现在 ProductId 的左边,因为其在元数据里的定义,位置在 ProductId 之前。
至此我们已经了解了 Smart Table 表格列项目渲染的逻辑,最后来看看源代码实现。
我的 UI5 应用里,使用了 Smart Table 控件的 XML 视图,运行时被加载后,会被 UI5 的 XML 模板解析器, XMLTemplateProcessor 的方法 parseTemplate 所解析。XML 视图包含的 XML 字符串,会被反序列化成 DOM 并进行遍历。当模板解析器遍历 DOM 过程中,遇到 SmartTable 标签时,调用 SmartTable.init 方法,进行初始化操作:
根据本文前半部分的介绍,我们已经知道:如果缺乏 OData 元数据提供的注解,Smart Table 控件无法知道该怎么渲染表格的列项目。因此,SmartTable.js 也在 “OData 服务元数据成功取回” 这个事件上,注册了一个钩子函数 _onMetadataInitialised. 当 OData 服务的元数据取回之后,该回调函数被调用:
SmartTable.prototype._onMetadataInitialised = function() { this._bMetaModelLoadAttached = false; if (this.bIsInitialised) { return; } this._bUseColumnLabelsAsTooltips = this.getUseColumnLabelsAsTooltips(); // keep value stable after initialization // Check whether further custom columns where added in the meantime this._updateInitialColumns(); this._fireBeforeInitialiseAndValidate(); this._validateCustomizeConfig(this.getCustomizeConfig()); this._createTableProvider(); if (!this._oTableProvider) { return; } this._aTableViewMetadata = this._oTableProvider.getTableViewMetadata(); if (!this._aTableViewMetadata) { return; } if (this._bUseColumnLabelsAsTooltips) { this._oTable.getColumns().forEach(function(oColumn) { var oHeader = null; if (oColumn.getHeader) { oHeader = oColumn.getHeader(); } else if (oColumn.getLabel) { oHeader = oColumn.getLabel(); } var oLabel = oHeader && oHeader.isA && (oHeader.isA(".m.Label") || oHeader.isA(".m.Text")) ? oHeader : null; var oTooltipTarget = this._isMobileTable ? oLabel : oColumn; var oTooltip = oTooltipTarget ? oTooltipTarget.getTooltip() : null; if (oTooltipTarget && oLabel && !oTooltip && !oTooltipTarget.isBound("tooltip")) { if (oLabel.isBound("text")) { var oBindingInfo = _getClonedBindingInfo(oLabel.getBindingInfo("text")); oTooltipTarget.bindProperty("tooltip", oBindingInfo); } else { oTooltipTarget.setTooltip(oLabel.getText()); } } }, this); } // Set width for custom columns after metadata is initialized if (this.getEnableAutoColumnWidth()) { this._oTable.getColumns().forEach(this._setWidthForCustomColumn, this); } if (!this._isMobileTable && this.getDemandPopin()) { this.setDemandPopin(false); Log.error("use SmartTable property 'demandPopin' only with responsive table, property has been set to false"); } this.detachModelContextChange(this._initialiseMetadata, this); // Indicates the control is initialised and can be used in the initialise event/otherwise! this.bIsInitialised = true; delete this._bInitialising; this._updateP13nDialogSettings(true); this._bTableSupportsExcelExport = this._oTableProvider.getSupportsExcelExport(); this._bMultiUnitBehaviorEnabled = this._oTableProvider.getMultiUnitBehaviorEnabled(); this._listenToSmartFilter(); this._createVariantManagementControl(); // creates VariantMngmntCtrl if useVariantManagement OR useTablePersonalisation is true. // Control is only added to toolbar if useVariantManagement is set otherwise it acts as // hidden persistance helper this._createToolbarContent(); this._applyToolbarContentOrder(); this._aAlwaysSelect = this._oTableProvider.getRequestAtLeastFields(); this._createContent(); this._createPersonalizationController(); // Create a local JSONModel to handle editable switch this._oEditModel = new JSONModel({ editable: this.getEditable() }); // Set the local model on the SmartTable this.setModel(this._oEditModel, "sm4rtM0d3l"); this.attachEvent("_change", this._onPropertyChange, this); this.fireInitialise(); // Trigger initial binding if no Variant exists -or- if it is already initialised if (!this._oVariantManagement || (this._oVariantManagement && this._bVariantInitialised)) { this._checkAndTriggerBinding(); } };
该函数是 UI5 中 SmartTable
控件的一个方法,主要负责在元数据初始化完成后设置表格的一些核心功能和行为。此方法是内部方法,通常在控件初始化过程中自动调用。
该函数每个步骤都旨在确保 SmartTable
控件能够根据提供的配置和元数据正确初始化,提供灵活的个性化设置,并为最终用户呈现丰富、互动的数据表格视图。通过这种方式, UI5 框架提供了强大的工具,支持开发人员创建高度定制的应用程序视图,满足复杂的业务需求。
在该回调函数执行的上下文里,因为 OData 服务元数据已经处于可访问状态,所以 Smart Table 有足够的信息,能够开始渲染逻辑的执行:
下图第 97 行的高亮代码,getLineItemAnnotation, 即是 Smart Table 控件,准备从 Product 这个 EntityType 里,解析出符合表格列项目渲染要求的字段列表:
注意下图第 1909 行硬编码的字符串 com…vocabularies.UI.v1.LineItem, 这就是 UI5 框架代码里查找 Smart Table 待渲染列表项字段的依据。最后解析出的两个列表项字段,Name 和 ProductId,就存储在函数返回变量 oResolvedAnnotation.
有了这个信息,Smart Table 就知道该渲染哪些字段作为表格列项目了。
至此,本文已经完成了 Smart Table 控件渲染表格列项目的原理介绍,以及相应的 UI5 框架是如何解析待渲染列项目的源代码实现的介绍。
希望本文能给对 Smart Table 技术内幕感兴趣的朋友们有所启发。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。