深入介绍 UI5 框架里 Smart Field 控件的工作原理

简介: 深入介绍 UI5 框架里 Smart Field 控件的工作原理

Fiori Elements 是 S/4HANA 这款世界领先的企业级管理页面的前端开发技术,Fiori Elements 的前身称为 Smart Template,里面重度使用了一种叫做 Smart Field 的控件。

Fiori Elements(Smart Template) 顾名思义,是一套设计理念别具一格的前端框架,针对 2B 领域最常用的应用模式,根据元数据注解(metadata Annotations),加上预定义的模板(Predefined Template),在运行时能够动态生成 Look-and-Feel 高度一致的 UI 页面。

Fiori Elements 的核心就是其基于的元数据注解,以及根据这些元数据,在运行时 智能地 生成 UI 的能力。由于这种运行时动态生成 UI 的行为发生在幕后,对应用开发人员来说完全是一个黑盒子。不少开发人员觉得 Fiori Elements 的工作原理很神秘,即使想研究其源代码,也不知道该如何入手。

到本文写作时为止, Fiori Elements 支持 5 种类型的预定义模板:

  • List report
  • Worklist
  • Object page
  • Overview page
  • Analytical list page

打开 List report 模板的 XML 视图实现源代码,能发现 smartfield 和 smarttable 的使用:

<template:elseif test="{= (${dataField>RecordType} === 'com..vocabularies.UI.v1.DataFieldWithNavigationPath')}">
      <!-- ObjectPage Self-Linking -->
      <smartField:SmartField
        value="{path: 'dataField>Value', formatter: '.suite.ui.generic.template.js.AnnotationHelper.getDataFieldValueSimplePath'}"
        editable="{ui>/editable}" press="._templateEventHandlers.onDataFieldWithNavigationPath"
        ariaLabelledBy="{columnId>id}">
        <smartField:customData>
          <core:CustomData key="Target" value="{dataField>Target/NavigationPropertyPath}" />
        </smartField:customData>
        <smartField:configuration>
          <smartField:Configuration
            displayBehaviour="{parts: [{path: 'dataField>'}, {value: ''}, {path: 'listEntitySet>'}], formatter: '.suite.ui.generic.template.js.AnnotationHelper.getTextArrangementForSmartControl'}" />
        </smartField:configuration>
      </smartField:SmartField>
    </template:elseif>
    <template:elseif test="{= (${dataField>RecordType} === 'com..vocabularies.UI.v1.DataFieldForAction' || ${dataField>RecordType} === 'com..vocabularies.UI.v1.DataFieldForIntentBasedNavigation') &amp;&amp; ${dataField>Inline/Bool} === 'true'}">
      <!-- handle inline actions -->
      <core:Fragment fragmentName=".suite.ui.generic.template.fragments.InlineButton" type="XML"/>
    </template:elseif>
    <template:elseif test="{= (${dataField>RecordType} === 'com..vocabularies.UI.v1.DataFieldWithIntentBasedNavigation')}">
      <!--handle DataFieldWithIntentBasedNavigation -->
      <fe:Link
        text="{parts: [{path: 'dataField>'}, {path: 'listEntitySet>'}], formatter: 'AH.getLinkTextForDFwithIBN'}"
        press="._templateEventHandlers.onDataFieldWithIntentBasedNavigation" wrapping="true">
        <fe:customData>
          <core:CustomData key="SemanticObject" value="{path: 'dataField>SemanticObject', formatter: '.ui.model.odata.AnnotationHelper.format'}"/>
          <core:CustomData key="Action" value="{path: 'dataField>Action', formatter: '.ui.model.odata.AnnotationHelper.format'}"/>
        </fe:customData>
      </fe:Link>
    </template:elseif>

二者都属于 Smart Controls,是构成 Fiori Elements 预定义模板的基石。

官网对 Smart Controls 的定义:一种特殊的 UI5 控件集合,能够通过解析 OData 元数据,给普通的 UI5 控件增添一些额外的功能。

要想搞清楚 Fiori Elements 的工作原理,理解 Smart Controls 是前置条件之一。而 Smart Field 是 Smart Controls 大家庭中最简单的类型,因此如果想研究 Smart Controls 的工作原理,Smart Field 是最佳的学习目标。

笔者开发了一个 Hello World 级别的 UI5 应用,XML 视图里仅仅包含一个 Smart Field.

应用的源代码地址如下

定义了 Smart Field 的 XML 视图源代码如下:

<mvc:View
  xmlns:mvc=".ui.core.mvc"
  controllerName=".ui.demo.smartControls.SmartField"
  xmlns:form=".ui.layout.form"
  xmlns=".m"
  xmlns:smartField=".ui.comp.smartfield">
  <form:SimpleForm 
    minWidth="1024"
    maxContainerCols="2"
    editable="true"
    layout="ResponsiveGridLayout"
    labelSpanL="3"
    labelSpanM="3"
    emptySpanL="4"
    emptySpanM="4"
    columnsL="1"
    columnsM="1"
    class="editableForm">
    <form:content>
      <smartField:SmartLabel labelFor="idPrice"/>
      <smartField:SmartField value="{Price}" id="idPrice"/>
      <Button
        text="Print"
        press="onPrintTriggered"/>
    </form:content>
  </form:SimpleForm>
</mvc:View>

将该应用从 Github 代码仓库下载到本地,node loca.js 运行后,访问如下 url,即可打开渲染后的页面。

该应用渲染出来的页面如下:

虽然我们在 XML 视图里只定义了一个 Smart Field 控件,但最后渲染出的页面里,居然包含了两个输入字段:

  • 价格金额 (Amount)
  • 价格的货币单位 (UnitCode)

另外,在 XML 视图里我并未指定 Price 字段的标签,那么最后界面里"Jerry的价格",到底是在哪里维护的呢?

这就是 Smart Controls 的神奇之处。

第 17 行 XML 视图里的 smartField 标签, id 属性我硬编码成 idPrice:

运行时,被 Smart Field 对应的 renderer,渲染成 div 标签,id 为 __xmlview0–idPrice.

那么渲染出来的页面里,另一个货币单位,即显示 EUR 的字段,在 XML 视图里根本没有定义,它到底是根据什么样的逻辑动态生成出来的?

既然前文已经提到,Smart Field 的一大特征就是能够解析 OData 元数据,并为自身增添新的功能,所以我们回过头仔细查看 XML 视图里 smartField 绑定的属性,其名称为 Price.

在该项目的元数据定义文件,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..wt01"
        :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"/>
        <Property Name="Price" Type="Edm.Decimal"
              :unit="CurrencyCode" :label="Jerry的价格"
              :updatable="true"/>
        <Property Name="CurrencyCode" Type="Edm.String"
              MaxLength="3" :label="Currency" :semantics="currency-code"
              :updatable="true"/>
      </EntityType>
      
      <EntityContainer m:IsDefaultEntityContainer="true"
               :supported-formats="atom json">
        <EntitySet Name="Products" EntityType="com..wt01.Product"
               :updatable="true"/>
      </EntityContainer>
    </Schema>
  </edmx:DataServices>
</edmx:Edmx>

第 10 行即是 XML 视图里 smartField 绑定的 Price 属性,如上图红色下划线所示。该属性具有一个命名空间为 的注解,注解名称为 unit,值为 CurrencyCode,意思是,该字段的单位,绑定到 OData 模型另一个名为 CurrencyCode 的字段,即上图红色箭头指向的第 17 行的属性。而我们在最终渲染页面里看到的标签"Jerry的价格",则通过另一个注解 :label 的值维护。

CurrencyCode 这个属性本身,用注解 :semantics 声明其语义为 currency-code.

如果把这个值修改一下,比如去掉中间的连接线,改成currencycode,则最后渲染出的页面如下图所示,货币单位字段将消失,说明 Smart Field 工作出了问题。

下面我们通过单步调试来搞清楚幕后到底发生了什么。

这是我使用的本地测试数据:

在运行时,该数据成功加载后,在数据加载成功的 UI5 框架回调函数里,调用 setElementBindingContext 函数, 进而调用 propagateProperties 函数,触发 Smart Field 的初始化处理逻辑。

注意观察下图右边的调用栈,propagateProperties 会把控制权交给 SmartField.js, 后者调用工厂方法 _createFactory, 根据解析出的 OData 注解,即 : 开头的注解,创建对应的普通 UI5 控件实例。所谓普通的 UI5 控件,即 .m 命名控件下的 UI5 控件。

XML 视图里定义的元数据注解,通过工具 AnnotationHelper.getXXX 实现。

比如:

  • getUnit, 查找属性上的 :unit 注解
  • getLabel, 查找属性上的 :label 注解
  • 以此类推

看到下图 AnnotationHelper.js 里 isCurrency 方法里第 136 行硬编码的 currency-code, 我们就能恍然大悟,明白为什么 XML 视图里把 :semantics 的注解值从 currency-code 改成其他值之后,Smart Field 就无法正常工作的原因了。

AnnotationHelper.js 把一个 OData 属性所有的注解解析完毕之后,交给ODataHelper,后者进行汇总,进行下一步处理。

下图展示了 XML 视图里关于 Price 和 CurrencyCode 两个 OData 属性,其元数据注解均已解析完毕。

最后,在 ODataControlFactory 这个工厂实现里,直接使用 JavaScript 关键字 new,新建一个普通的 .m.Input 控件实例,然后再由其渲染器生成原生的 HTML input 标签。该标签的 id 为 其父节点的 id 加上 -input 后缀。

下图就是最后渲染而成的 input 标签。

总结

通过本文介绍的具体例子,我们能够直观地感受到,较之其在 XML 视图里的定义相比,Smart Field 运行时能够渲染出内容丰富得多的页面,而这些页面,极度依赖于 Smart Field 绑定到的 OData 属性上定义的以 : 作为前缀的元数据注解。

希望通过本文的介绍,大家对于 Smart Field 的工作原理和作用,能相比纯粹阅读 官网上的帮助文档,有一个更深入的理解。

相关文章
|
4天前
|
C# 开发者 Windows
基于Material Design风格开源、易用、强大的WPF UI控件库
基于Material Design风格开源、易用、强大的WPF UI控件库
|
4天前
|
人工智能 前端开发 搜索推荐
前端UI框架的发展:从混沌到秩序的演变
前端UI框架的发展:从混沌到秩序的演变
|
4天前
|
算法 API C++
【Qt UI】QT 窗口/控件置顶方法详解
【Qt UI】QT 窗口/控件置顶方法详解
87 0
|
3天前
|
Web App开发 开发框架 前端开发
Open UI5 前端开发框架配套的 Mock Server 工作原理解析
Open UI5 前端开发框架配套的 Mock Server 工作原理解析
11 0
|
4天前
|
前端开发 Java Android开发
Android UI底层绘制原理
Android UI底层绘制原理
14 0
|
4天前
|
开发框架 前端开发 JavaScript
学会Web UI框架--Bootstrap,快速搭建出漂亮的前端界面
学会Web UI框架--Bootstrap,快速搭建出漂亮的前端界面
|
4天前
|
XML Java Android开发
Android之UI基础控件
Android之UI基础控件
|
4天前
|
前端开发 搜索推荐 开发者
SAP UI5 sap.m.Column 控件的 minScreenWidth 属性介绍
SAP UI5 sap.m.Column 控件的 minScreenWidth 属性介绍
33 0
|
4天前
|
JavaScript 前端开发 开发者
SAP UI5 控件 sap.m.ListBase 的 inset 属性的作用介绍
SAP UI5 控件 sap.m.ListBase 的 inset 属性的作用介绍
18 0
|
4天前
|
前端开发 JavaScript API
SAP UI5 sap.ui.require.toUrl 的作用介绍
SAP UI5 sap.ui.require.toUrl 的作用介绍
37 0

热门文章

最新文章