实现视图同步
学习目标
实现列表视图与明细视图之间的同步功能。
实现导航
实现导航
当您使用 sap.f.FlexibleColumnLayout 实现应用程序时,同样也会使用基本导航原则——例如使用路由器对象或为 attachMatched 注册事件处理器。不同之处在于,我们必须用 sap.f.FlexibleColumnLayout 控件对其进行扩展。

该图展示了与已知的导航目标控制器内部导航处理方式的区别。如您在实现中所见,id 为 mainView 的模型是从组件中获取的。在同一行代码中,layout 被设置为 TwoColumnsMidExpanded。这将向用户显示 sap.f.FlexibleColumnLayout 的第二列。
同步列表视图与明细视图
最终用户通常会选择列表中的某个项以显示详细信息。如果是这样,一切都会按预期工作。导航被触发,所选项的详细信息显示在明细视图中,并且所选项在列表中被标记为已选中。但是,如果用户直接在浏览器地址栏中更改哈希值,会发生什么呢?
显然,如果哈希值有效,则会显示详细信息,但列表中的项不会被标记为已选中。此外,如果哈希值无效,则会显示 NotFound 视图,但先前选中的项仍然被标记为已选中。为实现这一点,我们必须在明细视图与列表视图之间实现同步功能。
在本视频中,我们将看看如何实现同步功能。
视频:本节含 SAP 官方视频(视频 ID
1_vc8qxs87),需在 learning.sap.com 在线观看。
为列表-明细应用程序提供视图同步
业务示例
在本练习中,您将实现列表视图与明细视图的同步。
注意
SAP Business Technology Platform 及其工具是云服务。由于云软件的特性,步骤流程以及字段和按钮的命名可能与练习解决方案有所不同。
前提条件
您需要已完成先前关于构建明细视图的练习。
任务 1:提供视图同步功能
在本任务中,您将通过创建一个名为 ListSelector 的类来实现视图同步。ListSelector 的实现应放置在项目的 *controller* 文件夹中。*ListSelector.js* 文件已在课程模板文件夹中提供。
步骤
将
*ListSelector.js*文件上传到您的项目中。打开 controller 文件夹的上下文菜单,然后选择 Upload。
从您的
*Templates*文件夹中选择*ListSelector.js*文件。观察
*ListSelector.js*文件的内容,并回答以下问题。 ListSelector 类派生自哪个对象? 主列表控制器在初始化时会调用 setBoundMasterList 函数。它做什么? 随后主列表控制器会等待 oWhenListLoadingIsDone promise 被兑现,然后才显示第一个承运人明细视图。为什么? SelectAListItem 函数做什么?查看 ListSelector.js 文件。 ListSelector 类派生自 BaseObject 类。 setBoundMasterList 函数将 _oList 变量设置为承运人列表,并兑现 _oWhenListHasBeenSet promise。 当 _oWhenListHasBeenSet promise 被兑现时所调用的函数会等待绑定对象上触发 dataReceived 事件,然后,如果存在数据,则设置 resolve 函数的
list和oFirstListItem参数。随后主列表控制器就能够显示第一个元素的明细视图。为确保
ListSelector可以从应用程序的每个部分引用,我们必须在更全局的层级上创建ListSelector的实例。因此,请在*Component.js*文件内创建ListSelector的实例,并确保该实例是组件的成员变量。然后保存您的更改。打开项目 controller 文件夹中的
*Component.js*文件。按如下方式在
*Component.js*文件中添加对ListSelector的引用:
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/Device",
"student/com/sap/training/advancedsapui5/listdetail/model/models",
"student/com/sap/training/advancedsapui5/listdetail/controller/ListSelector"
],
function (UIComponent, Device, models, ListSelector) {
在 Component 的
init函数中,声明一个名为oListSelector的成员变量,并将一个ListSelector实例赋给该变量。然后保存您的更改。
// instantiation of the listselector
this.oListSelector = new ListSelector();
您的
init函数现在应如下所示:
任务 2:实现基础控制器
在本任务中,您将实现一个基础控制器类。该类向所有控制器提供一些可复用的功能(用于路由、视图同步和后退导航)。
步骤
将
*BaseController.js*文件上传到您的项目中。打开 controller 文件夹的上下文菜单,然后选择 Upload。
从您的
*Templates*文件夹中选择*BaseController.js*文件。查看
getListSelector函数。它返回组件中oListSelector对象的引用。
任务 3:实现列表控制器
在本任务中,您将实现 List 视图的控制器以用于导航。
步骤
打开
*Carrier.controller.js*文件。定义该控制器应派生自BaseController。移除用于全屏导航的两个函数。打开项目
controller文件夹中的*Carrier.controller.js*文件。按如下方式更新代码:
sap.ui.define([
"student/com/sap/training/advancedsapui5/listdetail/controller/BaseController"
],
function (Controller) {
"use strict";
return Controller.extend("student.com.sap.training.advancedsapui5.listdetail.controller.Carrier", {
});
});
实现一个
_navigateToCarrierDetails函数。该函数接受两个参数。将第一个参数命名为sCarrierId,第二个命名为bReplace。该函数应导航到carrierdetails路由,并传入一个sCarrierId值作为参数。添加以下代码:
_navigateToCarrierDetails : function(sCarrierId,bReplace) {
this.getRouter().navTo("flights", {
carrid: sCarrierId
}, bReplace);
},
添加一个
_showDetail函数。该函数接受一个参数。将参数命名为oItem。该变量在运行时会包含用户从主列表中选择的项。从oItem中读取属性Carrid并将其存储在变量sCarrierId中。同时检查应用程序是否运行在移动设备上。将结果存储在局部变量bReplace中。调用_navigateToCarrierDetails函数,并将这两个变量作为参数传入。在
_navigateToCarrierDetails实现之后,按如下方式添加带oItem参数的_showDetail函数:
_showDetail: function(oItem) {
},
检查应用程序是运行在移动设备上还是台式机上,并将结果存储在局部变量中。然后从
oItem对象的bindingContext中读取Carrid属性,并将其存储在一个变量中。将这两个变量传递给_navigateToCarrierDetails函数。将以下代码添加到_showDetail函数中:
var sCarrierId = oItem.getBindingContext().getProperty("Carrid");
this._navigateToCarrierDetails(sCarrierId,true);
实现列表 select 事件的事件处理器。用于处理该事件的赋值函数名为
onSelect。调用_showDetail方法并将所选项传递给该函数。表格的选择行为取决于应用程序是否运行在移动设备上。在
_showDetail实现之后添加以下代码:
onSelect: function(oEvent) {
this._showDetail(oEvent.getParameter("listItem") || oEvent.getSource());
},
实现一个
onBypassed函数。该函数应处理路由器绕过目标的情况。在该函数内部,对列表对象调用removeSelections函数。在
onSelect实现之后添加以下代码:
onBypassed: function() {
this._oList.removeSelections(true);
},
向实现中添加一个
_onListMatched函数。该函数不接受参数。当路由器调用list路由时,该函数将被调用。对 promiseoWhenListLoadingIsDone被兑现的情况作出反应。如果该 promise 被兑现,则应在明细视图内显示主列表中第一个项的详细信息。在
onBypassed实现之后添加以下代码:
_onListMatched: function() {
this.getListSelector().oWhenListLoadingIsDone.then(
function(mParams) {
var sObjectId = mParams.oFirstListItem.getBindingContext().getProperty("Carrid");
this._navigateToCarrierDetails(sObjectId,true);
}.bind(this)
);
}
实现
onInit函数。获取对您视图中主列表的引用,并将该引用存储在名为oList的变量中。将oList的引用赋给名为_oList的成员变量。使用addEventDelegate-function为视图的onBeforeFirstShow添加一个事件处理器,并调用ListSelector的setBoundMasterList,将oList对象的引用传递给该函数。实现当路由器调用masterlist路由时的行为。如果是这种情况,应调用_onListMatched函数。实现当路由器绕过list路由时的行为,在该事件发生时注册onBypassed函数。然后保存您的更改。在开头添加以下代码:
onInit: function () {
var oList = this.byId("list");
this._oList = oList;
this.getView().addEventDelegate({
onBeforeFirstShow: function () {
this.getOwnerComponent().oListSelector.setBoundMasterList(this._oList);
}.bind(this)
});
this.getRouter().getRoute("Overview").attachPatternMatched(this._onListMatched, this);
this.getRouter().attachBypassed(this.onBypassed, this);
},
保存您的更改。
任务 4:实现明细控制器
在本任务中,您将实现 Detail 视图的控制器以用于导航。
步骤
打开
*Flights.controller.js*文件。定义该控制器应派生自BaseController。由于我们使用基础控制器的 getrouter 和 onNavBack 函数,您现在可以移除这些函数。打开项目 controller 文件夹中的
*Flights.controller.js*文件。按如下方式更新代码:
sap.ui.define([
"student/com/sap/training/advancedsapui5/listdetail/controller/BaseController"
],
function (Controller) {
移除
getrouter函数。移除
onNavBack函数。修改
_onBindingChange函数。如果视图未绑定到某个实体,则应清除主列表上的选择。要清除选择,请在ListSelector上调用函数clearListSelection。如果视图绑定到某个实体,则获取该实体的绑定路径,并调用ListSelector中的selectAListItem函数。将绑定路径作为参数传入。您的代码应如下所示:
_onBindingChange: function() {
var oView = this.getView();
var oElementBinding = oView.getElementBinding();
if (!oElementBinding.getBoundContext()) {
this.getRouter().getTargets().display("notFound");
this.getOwnerComponent().oListSelector.clearMasterListSelection();
return;
}
var sPath = oElementBinding.getPath();
this.getOwnerComponent().oListSelector.selectAListItem(sPath);
}
修改名为
_onObjectMatched的函数。将 mainView Model 的 layout 属性改为TwoColumnsMidExpanded,以调整sap.f.flexibleColumnLayout的布局行为。从bindElement调用中移除dataRequested和dataReceived事件。视图的 Busy 属性必须以不同方式管理,因为每次选择更改时不会都调用 OData 服务。您的代码现在应如下所示::
_onObjectMatched: function (oEvent) {
var oArgs = oEvent.getParameter("arguments");
this._sCarrierId = oArgs.carrid;
var oView = this.getView();
oView.getModel("mainView").setProperty("/layout", "TwoColumnsMidExpanded");
oView.bindElement({
path: "/UX_C_Carrier_TP('" + this._sCarrierId + "')",
events: {
change: this._onBindingChange.bind(this)
}
});
},
修改
onInit函数。获取 OData Model 引用以附加 RequestSent 和 RequestCompleted 事件,并设置视图的 Busy 属性。按如下方式更新
onInit函数的代码:
onInit: function () {
var oRouter = this.getRouter();
oRouter.getRoute("flights").attachMatched(this._onObjectMatched, this);
var oModel = this.getOwnerComponent().getModel();
oModel.attachRequestSent(function (oEvent) {
this.getView().setBusy(true);
}, this);
oModel.attachRequestCompleted(function (oEvent) {
this.getView().setBusy(false);
}, this);
},
保存您的更改。
任务 5:测试您的应用程序
步骤
测试您的应用程序。选择一个承运人以查看相应的航线。

按先前练习中所示执行此步骤。
尝试导航到不存在对应路由的哈希值。应显示 NotFound 视图。

将浏览器中的 URL 改为与任何路由都不对应的内容,例如将
Carriers/AA改为Carrier/AA。