這是 Jerry 2021 年的第 34 篇文章,也是汪子熙公衆号總共第 310 篇原創文章。
Jerry 前一篇文章 深入掌握 SAP Fiori Elements 工作原理系列之二:如何給 Fiori Elements 應用添加自定義按鈕 介紹了如何給 SAP Fiori Elements 應用的 Smart Table 工具欄裡,新增自定義按鈕,并實作其點選事件處理函數。

本文 Jerry 繼續介紹 SAP Fiori Elements 應用裡,Smart Table 控件的渲染原理。根據前一篇文章介紹的知識,SAP Fiori Elements List Report 的模闆,包含了 SmartTable.fragment.xml 這個頁面片段:
而該頁面片段的源代碼裡,使用了 Smart Table 控件:
為了把和 Smart Table 控件不相關的依賴都剝離開,以便于大家把注意力聚焦在 Smart Table 本身上,本文 Jerry 另外開發了一個 SAP UI5 應用,隻包含一個最簡單的 XML 視圖,裡面使用到了 Smart Table 控件。同時,我開發了一個簡單的 Mock 伺服器,讓該應用在請求 OData 服務中繼資料時,由 Mock 伺服器把本地工程事先準備好的 metadata.xml 檔案的内容,作為中繼資料響應,直接傳回給應用。這樣我可以友善地修改本地 metadata.xml 檔案,來達到各種測試目的。
關于如何在 SAP UI5 應用裡手動搭建 Mock 伺服器,請參考我這篇文章:何以 mock server 的方式本地啟動 SAP UI5 應用,使它不連接配接遠端伺服器端的 OData 服務.
本文這個用于示範 Smart Table 控件使用方法的 SAP UI5 應用,其完整源代碼在我的 Github 上。
該應用三個核心檔案:
metadata.xml: 該檔案的内容即 OData 服務的中繼資料,包含加上了 SAP Fiori Elements 系列的自定義注解,我們稍後會詳細研究該檔案内容。當 SAP UI5 應用向伺服器發起 OData 服務中繼資料請求時,該請求被 Mock 伺服器攔截,後者将 metadata.xml 的内容作為 OData 服務中繼資料響應,傳回給應用。
Products.json: 測試資料 (Mock Data). 當 SAP UI5 應用準備渲染 Smart Table 待顯示的業務資料時,會向遠端伺服器發起 OData batch 請求。該請求同樣會被 Mock 伺服器攔截,并将該 Products.json 的内容傳回給應用。
server.js: Jerry 開發的 Mock 伺服器實作。
XML 視圖裡定義了一個 Smart Table 控件,第 10 行代碼 entitySet=“Products”, 意思是讓該控件,在運作時"智能地" 将名稱為 Products 的 OData 模型裡,所有符合某種條件的字段,渲染成表格列項目。
這個包含了 Smart Table 控件的 SAP UI5 應用,最終渲染成包含如下三列的表格:産品 ID,價格 (含金額和貨币機關) 以及産品名稱。
我們打開 metadata.xml, 找到了 Product 模型包含的四個屬性字段。這四個屬性字段,都作為最後渲染出的列項目的備選字段。其中 Price 字段,通過屬性 sap:unit, 和 CurrencyCode 字段關聯起來,作為同一個表格備選列項目,其工作原理在 Jerry 之前的文章 深入掌握 SAP Fiori Elements 工作原理的前提條件:了解 Smart Field 裡介紹過。
盡管 Product 模型包含了 4 個字段作為表格備選列項目,但為什麼最終渲染出的頁面裡,我們隻看到了 3 個行項目?名為 Category 的字段為什麼沒能渲染成行項目?
答案在 metadata.xml 的注解區域。
SAP 幫助文檔提到,其所屬的 OData 模型被注解 com.sap.vocabiularies.UI.LineItem 修飾,且類型為 com.sap.vocabularies.UI.DataField 的字段,在運作時會被 SAP UI5 架構繪制成表格列項目。
為了驗證這個結論,我們對 metadata.xml 裡的中繼資料進行一些修改。比如現在隻定義兩個表格列項目,分别為ProductId 和 Name. 同時,我用 sap:label, 給屬性 ProductId 配置設定标簽為 “Jerry産品ID”:
運作時的效果:Name 清單項出現在 ProductId 的左邊,因為其在中繼資料裡的定義,位置在 ProductId 之前。
至此我們已經了解了 Smart Table 表格列項目渲染的邏輯,最後來看看源代碼實作。
我的 UI5 應用裡,使用了 Smart Table 控件的 XML 視圖,運作時被加載後,會被 SAP UI5 的 XML 模闆解析器, XMLTemplateProcessor 的方法 parseTemplate 所解析。XML 視圖包含的 XML 字元串,會被反序列化成 DOM 并進行周遊。當模闆解析器周遊 DOM 過程中,遇到 SmartTable 标簽時,調用 SmartTable.init 方法,進行初始化操作:
根據本文前半部分的介紹,我們已經知道:如果缺乏 OData 中繼資料提供的注解,Smart Table 控件無法知道該怎麼渲染表格的列項目。是以,SmartTable.js 也在 “OData 服務中繼資料成功取回” 這個事件上,注冊了一個鈎子函數 _onMetadataInitialised. 當 OData 服務的中繼資料取回之後,該回調函數被調用:
在該回調函數執行的上下文裡,因為 OData 服務中繼資料已經處于可通路狀态,是以 Smart Table 有足夠的資訊,能夠開始渲染邏輯的執行:
下圖第 97 行的高亮代碼,getLineItemAnnotation, 即是 Smart Table 控件,準備從 Product 這個 EntityType 裡,解析出符合表格列項目渲染要求的字段清單:
注意下圖第 1909 行寫死的字元串 com.sap.vocabularies.UI.v1.LineItem, 這就是 UI5 架構代碼裡查找 Smart Table 待渲染清單項字段的依據。最後解析出的兩個清單項字段,Name 和 ProductId,就存儲在函數傳回變量 oResolvedAnnotation.
有了這個資訊,Smart Table 就知道該渲染哪些字段作為表格列項目了。
至此,本文已經完成了 Smart Table 控件渲染表格列項目的原理介紹,以及相應的 SAP UI5 架構是如何解析待渲染列項目的源代碼實作的介紹。
七年前,Jerry 剛剛從 ABAP 開發轉到 SAP UI5 開發時,對本文介紹的這些注解概念,了解得似是而非,因為之前用的 ABAP 這門程式設計語言,無法像 Java 和 TypeScript 那樣,能夠從語言層面提供對注解的原生支援。
後來接觸了 Java Spring 架構,再加上最近使用 Angular 做開發後,對注解的了解也比之前單純閱讀 SAP 文檔要深入一些了。在 Java,Angular 和 SAP Fiori Elements 裡,雖然這些注解的文法有差異,但目的都一緻,即提供一種對注解的目标對象,進行額外資料标注的功能。
比如 Component 是 Angular UI 最基本的組成單元,而 Component 的定義,無非就是普通的 TypeScript class,加上 @Component 注解的修飾而成。該注解能維護 Component 的中繼資料,告訴 Angular 架構,該 Component 在運作時應該如何被執行個體化和使用。
希望本文能幫助大家更好地了解 SAP 幫助文檔上對 SAP Fiori Elements 相關 OData 注解的介紹内容。感謝閱讀。