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

简介: 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 视图的完整源代码:

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

相关文章
|
4天前
|
Java
并发编程之线程池的底层原理的详细解析
并发编程之线程池的底层原理的详细解析
15 0
|
1天前
|
安全 索引
【集合】03 Linkedlist原理深入解析
【集合】03 Linkedlist原理深入解析
6 0
|
1天前
|
Java Spring 容器
SpringBoot自动装配原理之@Import注解解析
SpringBoot自动装配原理之@Import注解解析
|
4天前
|
缓存 JavaScript 前端开发
|
4天前
|
SQL 分布式计算 资源调度
一文解析 ODPS SQL 任务优化方法原理
本文重点尝试从ODPS SQL的逻辑执行计划和Logview中的执行计划出发,分析日常数据研发过程中各种优化方法背后的原理,覆盖了部分调优方法的分析,从知道怎么优化,到为什么这样优化,以及还能怎样优化。
|
1天前
|
XML 人工智能 Java
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
|
10天前
yolo-world 源码解析(六)(2)
yolo-world 源码解析(六)
19 0
|
10天前
yolo-world 源码解析(六)(1)
yolo-world 源码解析(六)
13 0
|
10天前
yolo-world 源码解析(五)(4)
yolo-world 源码解析(五)
22 0
|
10天前
yolo-world 源码解析(五)(1)
yolo-world 源码解析(五)
31 0

推荐镜像

更多