您的位置:首页 > 产品设计 > UI/UE

SAPUI5 (22) - Routing 实现多页面导航

2017-02-25 15:11 211 查看
前面我们实现了基于组件的多页面程序,这个程序还有两个主要的缺点:

1)存在全局变量
oApp (sap.m.App)
;

2)多个页面全部在程序启动时加载,多个页面也是在同一个 URL 下由

Openui5 框架进行管理,这种模式满足不了多页面的需求。解决的办法是:路由 ( routing ) 。通过 routing 实现多页面的导航,异步实现按需加载。

本篇仍然基于之前 Master-detail 供应商显示这支程序进行重构,使用 routing 实现页面间导航,将 Component 的元数据(metadata),移到专门的文件中。

OpenUi5 的 Routing

Openui5 的 routing 基于模式 ( pattern ),使用
#
符号表示不同的路径 ( route ),导航通过路径的改变来实现。

Pattern 表达式

Openui5 一共有 5 种
pattern表达式
:

硬编码模式:页面之间根据模式导航,没有参数传递,比如

product/settings
表示导航到产品配置。

路径含有必输参数模式:模式中 大括号({}) 包含的部分表示参数必须输入。比如
product/{id}
表示导航到产品某一 id,比如
product/5
表示 id 为 5 的产品,id 为必输。

路径含有可选参数模式:模式中 冒号 包含的部分为必输参数。比如
product/{id}/detail/:detailId:
detailId
为可选参数。
product/5/detail


以及
product/3/detail/2
都能与此模式匹配。

路径含有查询参数模式:查询参数 ( query parameter ) 在问号之后。比如
product{?query}
,query 这个参数为必输项。
product:?query:
中的 query 这个参数为可选参数。

通配参数模式:以星号结尾的参数是通配参数,通配参数将根据模式尽可能匹配。

Routing 实现 Master-detail 界面

下图来自网络,很好地说明了 routing 中的项目文件结构:

Application Descriptor

manifest.json
文件配置应用程序的很多信息,被称为 Application Descriptor 。先给出文件的全部内容:

{
"_version": "1.1.0",
"sap.app": {
"_version": "1.1.0",
"id": "stone.sapui5.test",
"type": "application",
"i18n": "i18n/i18n.properties",
"applicationVersion": {
"version": "1.0.0"
},
"title": "{{appTitle}}",
"description": "{{appDescription}}",
"dataSources": {
"mainService": {
"uri": "./service/data.json",
"type": "JSON"
}
}
},

"sap.ui": {
"_version": "1.1.0",
"technology": "UI5",
"deviceTypes": {
"desktop": true,
"tablet": true,
"phone": true
},
"supportedThemes": [
"sap_bluecrystal"
]
},

"sap.ui5": {
"_version": "1.1.0",
"rootView": {
"viewName": "webapp.view.App",
"type": "XML"
},
"dependencies": {
"minUI5Version": "1.30.0",
"libs": {
"sap.m": {}
}
},
"contentDensities": {
"compact": true,
"cozy": true
},
"models": {
"": {
"dataSource": "mainService"
},
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"settings": {
"bundleName": "webapp.i18n.i18n"
}
}
},
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "webapp.view",
"controlId": "app",
"controlAggregation": "pages",
"bypassed": {
"target": "notFound"
}
},
"routes": [{
"pattern": "",
"name": "master",
"target": "master"
},
{
"pattern": "detail/{supplierPath}",
"name": "detail",
"target": "detail"
}],
"targets": {
"master": {
"viewName": "Master",
"viewLevel": 1
},
"detail": {
"viewName": "Detail",
"viewLevel": 2
},
"notFound": {
"viewName": "NotFound",
"viewId": "notFound"
}
}
}
}
}


解释一些重要的配置:

1. 资源包文件

资源包文件的设置有两个地方:

"sap.app": {
"_version": "1.1.0",
"id": "stone.sapui5.test",
"type": "application",
"i18n": "i18n/i18n.properties",
...
},


这里设置的是资源包文件的路径和文件名。使用的相对于
manifest.json


文件的相对路径。

另外一个地方在
sapui5.models
:

"sap.ui5": {
...
"models": {
...
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"settings": {
"bundleName": "webapp.i18n.i18n"
}
}
}


设置名称为 i18n 的 resource model
bundleName
后面是根据

index.html
文件的 resource roots 设置的相对路径。然后在代码中添加对
ResourceBundle
的依赖后,通过
{i18n>xxx}
实现绑定。

2. Models

manifest.json
文件共设置了两个 model:

"sap.ui5": {
...
"models": {
"": {
"dataSource": "mainService"
},
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"settings": {
"bundleName": "webapp.i18n.i18n"
}
}
}


一个是没有指定名称的 model,当 view 中数据绑定时,没有给出前缀的时候,就参照到这个 model。比如
<Text text="{/Suppliers/0/id}" />
就参照到 model 所加载的数据中第一个 Supplier id。这个 model 的

dataSource
是在
sap.app
部分设置的 dataSource:

"sap.app": {
...
"dataSources": {
"mainService": {
"uri": "./service/data.json",
"type": "JSON"
}
}


dataSource 为
./service
文件夹下面的
data.json
文件。

第二个是刚才提到的 Resource Model: i18n。

3. Root View

"sap.ui5": {
"_version": "1.1.0",
"rootView": {
"viewName": "webapp.view.App",
"type": "XML"
},


Root view (启动即显示的 view):类型为 xml,名称为 App。OpenUI5 在相应文件夹下面查找名为
App.view.xml
文件并加载。通过这种方式,实现了 root view 的配置化。

Root view 是程序启动的重要设置,启动的流程如下:

index.html 的 ComponentContainer 根据
name
component
属性实例化 Component

Component 的 metadata 指向设定的
manifest.json
文件

manifest.json
文件的
sap.ui5>rootView
设定了启动时候加载并显示

的 root view 为
App.view.xml


App view 并不需要像之前文章介绍的内嵌 master view 和 detail view,而是由路由器根据路径在 pattern 中找匹配的模式,在 target 中找对应的

view 加载。

4. Routing 设置

"sap.ui5": {
...
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "webapp.view",
"controlId": "app",
"controlAggregation": "pages",
"bypassed": {
"target": "notFound"
}
},
"routes": [{
"pattern": "",
"name": "master",
"target": "master"
},
{
"pattern": "detail/{supplierPath}",
"name": "detail",
"target": "detail"
}],
"targets": {
"master": {
"viewName": "Master",
"viewLevel": 1
},
"detail": {
"viewName": "Detail",
"viewLevel": 2
},
"notFound": {
"viewName": "NotFound",
"viewId": "notFound"
}
}
}


比较直观,不懂的地方可以参照 Routing Configuration 。

Componet.js 文件

这个文件主要的变化是将 metadata 的设置、resource model 的设置、root view 的设置都转移到
manifest.json
中,所以 Component 中 一条语句完成初始化:

this.getRouter().initialize();


manifest.json
文件的全部代码:

sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/model/resource/ResourceModel",
"sap/ui/model/json/JSONModel"

], function (UIComponent, ResourceModel, JSONModel) {
"use strict";

return UIComponent.extend("webapp.Component", {

metadata: {
manifest: "json"
},

init : function () {
// call the base component's init function
UIComponent.prototype.init.apply(this, arguments);

// create the views based on the url/hash
this.getRouter().initialize();
}
});
});


Root View

<core:View xmlns:core="sap.ui.core"
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
displayBlock="true"
xmlns:html="http://www.w3.org/1999/xhtml">
<App id="app" />
</core:View>


根据
manifest.json
的 root view 设置,
App.view.xml
是 root view,在

view 中只需要申明
sap.m.App
,id 为
app
。Master view 和 Detail view不申明,由 routing 根据路径自动加载。

Master Controller 和 Detail Controller

Master view 和 Detail view 代码没有改变。但 Master controller 和 Detail controller 的代码需要改变。前一篇是通过 oApp(
sap.m.Agg
) 这个全局变量来导航,通过 oApp 管理页面。回顾一下 Master controller 中
onListPess
事件处理程序的代码:

onListPress: function(oEvent){
// 跳转到detail view
var sPageId = oApp.getPages()[1].getId();
oApp.to(sPageId);

// 设置detail page的bindingContext
var oContext = oEvent.getSource().getBindingContext();
var oDetailPage = oApp.getPage(sPageId);
oDetailPage.setBindingContext(oContext);
}


变更后 Master controller 的代码如下:

sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/core/UIComponent"
],

function(Controller, UIComponent){
"use strict";

return Controller.extend("webapp.controller.Master", {

onListPress: function(oEvent){
var oRouter = UIComponent.getRouterFor(this);
var oItem = oEvent.getSource();
var sPath = oItem.getBindingContext().getPath();
oRouter.navTo("detail", {
supplierPath: encodeURIComponent(sPath)
});;
}
});
}
);


Master controller 在行项目被点击之后,要完成两个任务:

- 跳转到 Detail view

- 向 Detail view 传递一个参数,这个参数是当前点击的路径,Detail view

获取这个路径,完成数据的绑定。

所有这些,都通过 router 来完成。

-
var oRouter = UIComponent.getRouterFor(this);
获取当前的 router

-
var oItem = oEvent.getSource()
获取点击所在的行,然后

oItem.getBindingContext().getPath()
获取点击行的路径 (string类型) 。比如,当用户点击第一行,sPath 为
/Suppliers/0
。这个路径需要传递到

detail view。

-
oRouter.navTo()
方法不能包含
/
(这是一个特殊的字符),否则提示如下错误。

Uncaught Error: Invalid value "/Suppliers/0" for segment "{supplierPath}".
...


所以使用
encodeURIComponent()
函数编码,在Detail controller 中用
decodeURIComponen()t
函数解码。

Detail.controller.js 的代码:

sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/core/UIComponent",
"sap/ui/core/routing/History"
],

function(Controller, UIComponent, History){
"use strict";

return Controller.extend("webapp.controller.Detail", {
onInit: function(){
var oRouter = UIComponent.getRouterFor(this);
oRouter.getRoute("detail")
.attachPatternMatched(this._onObjectMatched, this);
},

onNavPress: function() {
var oHistory = History.getInstance();
var sPreviousHash = oHistory.getPreviousHash();

if (sPreviousHash != undefined){
window.history.go(-1);
}else{
var oRouter = UIComponent.getRouterFor(this);
oRouter.navTo("master",{}, true);
}
},

_onObjectMatched: function (oEvent) {
var sPath = decodeURIComponent(
oEvent.getParameter("arguments").supplierPath);
this.getView().bindElement({ path: sPath});
}

});
}
);


代码说明:

Detail view 主要负责两件事:

1) 获取 Master view 传递的路径,根据此路径完成 element binding。比如当 Master view 传过来
/Suppliers/0
,则与第一条数据绑定;

2) 根据页面之间的关系,当点击 返回 按钮时,返回到上一个页面。

onInit()
event handler中:
oRouter.getRoute("detail").attachPatternMatched(this._onObjectMatched, this);
,当模式匹配时,附加事件处理器为
_onObjectMatched
。然后在
_onObjectMatched
中获取 Master view 传递的路径并绑定数据。

_onObjectMatched: function (oEvent) {
var sPath = decodeURIComponent(
oEvent.getParameter("arguments").supplierPath);
this.getView().bindElement({path: sPath});
}


当用户点击导航按钮,判断是否有上一个路径 ( previous hash ),如果有就返回上一个路径,否则跳转到 Master view:

onNavPress: function() {
var oHistory = History.getInstance();
var sPreviousHash = oHistory.getPreviousHash();

if (sPreviousHash != undefined){
window.history.go(-1);
}else{
var oRouter = UIComponent.getRouterFor(this);
oRouter.navTo("master",{}, true);
}
}


源代码

22_zui5_routing_master_detail

参考

Routing and Navigation

Get started with SAPUI5 and Routing

[Routing with Parameters](

https://openui5.hana.ondemand.com/#docs/guide/2366345a94f64ec1a80f9d9ce50a59ef.html)

Nested UI Routing in OpenUI5

[Creating modularized SAPUI5 applications with XML Views, Routing and i18n](

https://www.nabisoft.com/tutorials/sapui5/creating-modularized-sapui5-applications-with-xml-views-routing-and-i18n)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: