Fiori Elements 框架里 Smart Table 控件工作原理的深入解析

我们在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 视图的完整源代码:

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 复制代码
<?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 服务的元数据取回之后,该回调函数被调用:

javascript 复制代码
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 技术内幕感兴趣的朋友们有所启发。

相关推荐
涔溪42 分钟前
Ecmascript(ES)标准
前端·elasticsearch·ecmascript
榴莲千丞1 小时前
第8章利用CSS制作导航菜单
前端·css
奔跑草-1 小时前
【前端】深入浅出 - TypeScript 的详细讲解
前端·javascript·react.js·typescript
羡与1 小时前
echarts-gl 3D柱状图配置
前端·javascript·echarts
guokanglun1 小时前
CSS样式实现3D效果
前端·css·3d
咔咔库奇1 小时前
ES6进阶知识一
前端·ecmascript·es6
前端郭德纲1 小时前
浏览器是加载ES6模块的?
javascript·算法
JerryXZR1 小时前
JavaScript核心编程 - 原型链 作用域 与 执行上下文
开发语言·javascript·原型模式
帅帅哥的兜兜1 小时前
CSS:导航栏三角箭头
javascript·css3
渗透测试老鸟-九青2 小时前
通过投毒Bingbot索引挖掘必应中的存储型XSS
服务器·前端·javascript·安全·web安全·缓存·xss