深入介紹 UI5 框架裡 Smart Field 控制元件的工作原理

發表於2024-02-13

 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 reportWorklistObject pageOverview pageAnalytical list page
圖片
新增圖片註釋,不超過 140 字(可選)開啟 List report 模板的 XML 檢視實現原始碼,能發現 smartfield 和 smarttable 的使用:
圖片
新增圖片註釋,不超過 140 字(可選)<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 控制元件增添一些額外的功能。

圖片
新增圖片註釋,不超過 140 字(可選)要想搞清楚 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,即可開啟渲染後的頁面。該應用渲染出來的頁面如下:
圖片
新增圖片註釋,不超過 140 字(可選)雖然我們在 XML 檢視裡只定義了一個 Smart Field 控制元件,但最後渲染出的頁面裡,居然包含了兩個輸入欄位:價格金額 (Amount)價格的貨幣單位 (UnitCode)另外,在 XML 檢視裡我並未指定 Price 欄位的標籤,那麼最後介面裡"Jerry的價格",到底是在哪裡維護的呢?這就是 Smart Controls 的神奇之處。
圖片
新增圖片註釋,不超過 140 字(可選)第 17 行 XML 檢視裡的 smartField 標籤, id 屬性我硬編碼成 idPrice:
圖片
新增圖片註釋,不超過 140 字(可選)執行時,被 Smart Field 對應的 renderer,渲染成 div 標籤,id 為 __xmlview0–idPrice.
圖片
新增圖片註釋,不超過 140 字(可選)那麼渲染出來的頁面裡,另一個貨幣單位,即顯示 EUR 的欄位,在 XML 檢視裡根本沒有定義,它到底是根據什麼樣的邏輯動態生成出來的?既然前文已經提到,Smart Field 的一大特徵就是能夠解析 OData 後設資料,併為自身增添新的功能,所以我們回過頭仔細檢視 XML 檢視裡 smartField 繫結的屬性,其名稱為 Price.在該專案的後設資料定義檔案,metadata.xml 裡,我們找到了一些端倪:
圖片
新增圖片註釋,不超過 140 字(可選)<?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 工作出了問題。
圖片
新增圖片註釋,不超過 140 字(可選)下面我們透過單步除錯來搞清楚幕後到底發生了什麼。 這是我使用的本地測試資料:
圖片
新增圖片註釋,不超過 140 字(可選)在執行時,該資料成功載入後,在資料載入成功的 UI5 框架回撥函式里,呼叫 setElementBindingContext 函式, 進而呼叫 propagateProperties 函式,觸發 Smart Field 的初始化處理邏輯。
圖片
新增圖片註釋,不超過 140 字(可選)注意觀察下圖右邊的呼叫棧,propagateProperties 會把控制權交給 SmartField.js, 後者呼叫工廠方法 _createFactory, 根據解析出的 OData 註解,即 : 開頭的註解,建立對應的普通 UI5 控制元件例項。所謂普通的 UI5 控制元件,即 .m 命名控制元件下的 UI5 控制元件。
圖片
新增圖片註釋,不超過 140 字(可選)XML 檢視裡定義的後設資料註解,透過工具 AnnotationHelper.getXXX 實現。 比如:getUnit, 查詢屬性上的 :unit 註解getLabel, 查詢屬性上的 :label 註解以此類推
圖片
新增圖片註釋,不超過 140 字(可選)看到下圖 AnnotationHelper.js 裡 isCurrency 方法裡第 136 行硬編碼的 currency-code, 我們就能恍然大悟,明白為什麼 XML 檢視裡把 :semantics 的註解值從 currency-code 改成其他值之後,Smart Field 就無法正常工作的原因了。
圖片
新增圖片註釋,不超過 140 字(可選)AnnotationHelper.js 把一個 OData 屬性所有的註解解析完畢之後,交給ODataHelper,後者進行彙總,進行下一步處理。 下圖展示了 XML 檢視裡關於 Price 和 CurrencyCode 兩個 OData 屬性,其後設資料註解均已解析完畢。
圖片
新增圖片註釋,不超過 140 字(可選)最後,在 ODataControlFactory 這個工廠實現裡,直接使用 JavaScript 關鍵字 new,新建一個普通的 .m.Input 控制元件例項,然後再由其渲染器生成原生的 HTML input 標籤。該標籤的 id 為 其父節點的 id 加上 -input 字尾。
圖片
新增圖片註釋,不超過 140 字(可選)下圖就是最後渲染而成的 input 標籤。
圖片
新增圖片註釋,不超過 140 字(可選)總結透過本文介紹的具體例子,我們能夠直觀地感受到,較之其在 XML 檢視裡的定義相比,Smart Field 執行時能夠渲染出內容豐富得多的頁面,而這些頁面,極度依賴於 Smart Field 繫結到的 OData 屬性上定義的以 : 作為字首的後設資料註解。希望透過本文的介紹,大家對於 Smart Field 的工作原理和作用,能相比純粹閱讀 官網上的幫助文件,有一個更深入的理解。

相關文章