上期我們完成了一個簡單的主從頁面,但是頁面是靜態的,不能交互,功能也很簡單,只有一個銷售訂單的列表。 我們今天就一鼓作氣把代碼全都寫完,由於本次的代碼量較大,所以只對重點代碼部分進行講解。 具體每個文件和代碼就不一一貼出來了,代碼都放在github中,需要的自行下載吧。
1 頁面導航
可以先把代碼下載到本地並跑起來,這樣可以對這個最佳實踐的程序有一個直觀的了解。
頁面導航如下:
銷售訂單列表(Master) -> 銷售訂單明細(Detail) -> 行項目明細(LineItem),在每個明細頁面都可以返回到上一層。
具體頁面之間的導航是如何實現的呢?
我們從頁面的入口 index.html
開始
var oView = sap.ui.view({ id : "app", viewName : "ui5.tutorial.bp.view.App", type : "JS", }); //... oView.placeAt('content');
這一段代碼初始化了一個叫做App的JS view,那我們就來看 App.view.js
// create app this.app = new sap.m.SplitApp(); // load the master page var master = sap.ui.xmlview("Master", "ui5.tutorial.bp.view.Master"); master.getController().nav = this.getController(); this.app.addPage(master, true); // load the empty page var empty = sap.ui.xmlview("Empty", "ui5.tutorial.bp.view.Empty"); this.app.addPage(empty, false);
App view中在 createContent
中創建了一個SplitApp,稍微說下SplitApp這個控件,當宿主是PC或者平台的時候這個控件默認包含兩個頁面容器,而當宿主是手機的時候又可以只包含一個頁面,所以一般主從結構的頁面可以用這個控件。
隨后,分別創建了兩個頁面,一個是Master頁面,另一個頁面是空白頁——作為沒有選中任何Master頁面中的數據時的默認頁面,最后把兩個頁面都加入到了SplitApp中。
到目前為止都是在上一篇中已經做過的,到這里,頁面已經可以展示了。但是我們今天要研究的是導航,所以接着往下。
SplitApp本身帶有 to()
這個函數,可以在已經加入其容器的頁面之間導航,但是我們現在的功能稍稍有點復雜,當點擊Master頁面的時候,要求導航到詳細頁面, 點擊詳細頁面的行項目可以進入到行項目的詳細頁面,對應每個詳細頁面可以返回至上一級頁面,同時可能還需要做一些其他邏輯上的處理,比如判斷和綁定數據,因此我們需要在原有的 to()
上增強一些功能並封裝給其他的控制器使用。
這些功能都集中定義在 App.controller.js
這個控制器中
to : function (pageId, context) { var app = this.getView().app; // load page on demand var master = ("Master" === pageId); if (app.getPage(pageId, master) === null) { var page = sap.ui.view({ id : pageId, viewName : "ui5.tutorial.bp.view." + pageId, type : "XML" }); page.getController().nav = this; app.addPage(page, master); jQuery.sap.log.info("app controller > loaded page: " + pageId); } // show the page app.to(pageId); // set data context on the page if (context) { var page = app.getPage(pageId); page.setBindingContext(context); } }, /** * Navigates back to a previous page * @param {string} pageId The id of the next page */ back : function (pageId) { this.getView().app.backToPage(pageId); }
重新封裝后的 to()
有兩個參數,一個是頁面id,另一個是上下文,當傳入的頁面不存在的時候,系統會首先初始化這個頁面並加入到當前的SplitApp中,隨后直接調用SplitApp的 to()
完成導航動作。 最后,如果上下文不為空的話,把這個上下文綁定到新的頁面中,這對於一個新頁面來說是非常有意義的。
back則直接復用SplitApp的 backToPage()
函數。
接下來我們再看其他的頁面如何復用定義在這里的導航函數的。
我們回過頭來再看 App.view.js
, 其中在初始化Master頁面之后,有這么一條語句
master.getController().nav = this.getController();
這條語句把當前的控制器賦給Master頁面的控制器的nav,這個nav只是用來存放App控制器的一個key,叫什么名字都行,這樣在Master頁面中就可以通過nav來調用App控制器的所有函數了。
同樣的,再回到 App.controller.js
中,看到這條語句 page.getController().nav = this;
也是類似的作用。
在首頁面剛剛初始化時,Detail頁面是沒有加載的,當點擊Master頁面中的某個SalesOrder的時候,Master頁面中的 handleListItemPress
被調用:
handleListItemPress : function (evt) { var context = evt.getSource().getBindingContext(); this.nav.to("Detail", context); }
首先獲得被點擊的item上綁定的上下文信息(數據),然后調用App的 to()
方法並傳入 Detail
告訴頁面要加載Detail這個頁面,同時把上下文數據傳遞過去, 接下來又回到了 to()
, Detail頁面被初始化,綁定上下文數據,加載到SplitApp中,然后導航成為當前顯示頁面。
以上,就完成了這個App的導航,可以看到有些繁瑣,並且需要顯式的初始化子頁面的時候獲得並共享App的控制器,另外,頁面之間的跳轉不會修改URL,無法將某個中間頁面存為bookmark,所以這種方式並不是UI5所推薦的, 但是作為初學者了解UI5的頁面導航機制還是非常的直觀,另外對於簡單的應用來說,如果頁面較少也未嘗不可以考慮。
作為稍大型的web應用,UI5在早期的版本中推薦使用EventBus通過Event的傳遞來實現復雜的頁面導航,從1.6開始引入了新的導航機制,就是Routing,可以將頁面之間的導航關系定義在component中,在最新的1.30版本中,導航定義則可以直接寫在App的說明文件 manifest.jso
中。
導航就介紹到這里,Component和Routing是一個比較復雜但是非常強大的工具,我們可以在后續接着探討。
2 數據綁定
在我們的代碼中,數據綁定也是做了簡化處理,都直接寫在 index.html
中了。
一共綁定了三個模型:
- 業務數據模型:
因為我們使用的是離線的json格式數據,所以可以直接把相對路徑傳遞給sap.ui.model.json.JSONModel
來初始化這個模型,並綁定到App這個根視圖上。var oModel = new sap.ui.model.json.JSONModel("model/mock.json"); oView.setModel(oModel);
隨后在這個視圖及其子視圖中,都可以直接通過類似
{SoId}
這種語法格式來使用這個模型的數據字段,需要注意的是,如果需要綁定的字段是這個模型的根節點,需要在前面加一個/
,譬如在Master視圖中綁定到列表的aggregate字段items
,是這樣的語法格式:items="{/SalesOrderCollection}" 。 - 多語言模型:
UI5中使用了i18n
機制來處理多語言問題。i18n是 internationalization的簡稱,在首位兩個字母之間有18個字母……具體如何使用非常的簡單,
首先創建一個資源文件messageBundle.properties
,這里我們在根目錄創建了一個i18n
目錄,在這里目錄中集中存放相關的i18n文件。
在這個資源文件里我們定義如下:MasterTitle=Sales Orders
DetailTitle=Sales Order
StatusTextN=New
StatusTextP=In Process
ApproveButtonText=Approve
…左邊的是KEY,右邊的是對應的語言描述,如果我們需要定義一個中文的語言文件,那么只需要拷貝這個文件並重命名為
messageBundle_zh-CN.properties
,並將對應的描述改為中文如下:
MasterTitle=銷售訂單列表 DetailTitle=銷售訂單 StatusTextN=新建 StatusTextP=處理中 ApproveButtonText=批准 …
系統會根據用戶設定的瀏覽器語言順序依次查找對應的語言資源文件,如果都找不到的話,就會找默認的
messageBundle.properties
。定義好了資源文件,我們接下來就在
index.html
中通過sap.ui.model.resource.ResourceModel
來初始化這個資源模型,接着就可以把它綁定到視圖或者控件中使用了。
怎么使用呢?在視圖中通過類似{i18n>MasterTiel}
這種語法格式來綁定到對應的空間的文本項上,實際使用中需要用引號把這個串包含進去。這個串中前面的i18n>
指的是引用綁定到本視圖的叫做i18n的模型,這里的i18n是綁定時起的名字,oView.setModel(i18nModel, "i18n");
可以是任意符合格式的字符串。
- 設備模型 設備模型通過查詢jQuery的device來獲悉宿主是否手機,並設定相應的不同顯示選項,然后將結果存為Json格式並初始化為一個JSON模型,最后綁定到模型中。
3 工具方法
大多數情況下,我們可以直接把業務數據直接綁定到控件中顯示,但是在一些情況下,我們可能需要對其中的一些格式做一些調整,或者根據一些字段做一些簡單的邏輯處理, 這個時候,我們就需要用到大多數控件中的某些屬性的 formatter
方法。
<ObjectStatus text="{ path: 'LifecycleStatus', formatter: 'ui5.tutorial.bp.util.Formatter.statusText' }" state="{ path: 'LifecycleStatus', formatter: 'ui5.tutorial.bp.util.Formatter.statusState' }" />
上面這個例子中,我們來看 text
屬性,如果我們希望直接把業務數據綁定到text中,我們這樣定義 text="{LifecycleStatus}",但是我們知道這個字段可能是后台定義的技術字段,我們需要把它轉化的比較有業務意義。 所以這個時候,我們就需要用到 formatter
了,首先定義 path
告之需要綁定的字段,這里不需要用大括號,隨后給 formatter
賦予一個處理方法,這個方法可以定義在任何地方,我們這里是在util下單獨定義了一個 Fomatter.js
來集中處理這類需求。
來看 Formatter.js
,我們就看 statusText
這個方法:
statusText : function (value) { var bundle = this.getModel("i18n").getResourceBundle(); return bundle.getText("StatusText" + value, "?"); },
path
中綁定的字段對應的值會作為參數傳入,然后用這個值結合StatusText生成一個KEY,並在 i18n
中取出相應的描述。
4 總結
基本上這個最佳實踐應用已經被剖析完成了,通過這樣一個最佳實踐 Best Practice
的練習,我們學習到了一般的UI5應用的整體結構以及大多數重要控件的使用方法。