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') && ${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 的工作原理和作用,能相比純粹閱讀 官網上的幫助文件,有一個更深入的理解。