实现视图同步

学习目标

  • 实现列表视图与明细视图之间的同步功能。

实现导航

实现导航

当您使用 sap.f.FlexibleColumnLayout 实现应用程序时,同样也会使用基本导航原则——例如使用路由器对象或为 attachMatched 注册事件处理器。不同之处在于,我们必须用 sap.f.FlexibleColumnLayout 控件对其进行扩展。

明细视图控制器中的 _onObjectMatched 函数

该图展示了与已知的导航目标控制器内部导航处理方式的区别。如您在实现中所见,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* 文件已在课程模板文件夹中提供。

步骤

  1. *ListSelector.js* 文件上传到您的项目中。

  2. 打开 controller 文件夹的上下文菜单,然后选择 Upload

  3. 从您的 *Templates* 文件夹中选择 *ListSelector.js* 文件。

  4. 观察 *ListSelector.js* 文件的内容,并回答以下问题。 ListSelector 类派生自哪个对象? 主列表控制器在初始化时会调用 setBoundMasterList 函数。它做什么? 随后主列表控制器会等待 oWhenListLoadingIsDone promise 被兑现,然后才显示第一个承运人明细视图。为什么? SelectAListItem 函数做什么?

  5. 查看 ListSelector.js 文件。 ListSelector 类派生自 BaseObject 类。 setBoundMasterList 函数将 _oList 变量设置为承运人列表,并兑现 _oWhenListHasBeenSet promise。 当 _oWhenListHasBeenSet promise 被兑现时所调用的函数会等待绑定对象上触发 dataReceived 事件,然后,如果存在数据,则设置 resolve 函数的 listoFirstListItem 参数。随后主列表控制器就能够显示第一个元素的明细视图。

  6. 为确保 ListSelector 可以从应用程序的每个部分引用,我们必须在更全局的层级上创建 ListSelector 的实例。因此,请在 *Component.js* 文件内创建 ListSelector 的实例,并确保该实例是组件的成员变量。然后保存您的更改。

  7. 打开项目 controller 文件夹中的 *Component.js* 文件。

  8. 按如下方式在 *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) {
  1. 在 Component 的 init 函数中,声明一个名为 oListSelector 的成员变量,并将一个 ListSelector 实例赋给该变量。然后保存您的更改。

// instantiation of the listselector
this.oListSelector = new ListSelector();
  1. 您的 init 函数现在应如下所示: 组件 init 函数的截图。

任务 2:实现基础控制器

在本任务中,您将实现一个基础控制器类。该类向所有控制器提供一些可复用的功能(用于路由、视图同步和后退导航)。

步骤

  1. *BaseController.js* 文件上传到您的项目中。

  2. 打开 controller 文件夹的上下文菜单,然后选择 Upload

  3. 从您的 *Templates* 文件夹中选择 *BaseController.js* 文件。

  4. 查看 getListSelector 函数。它返回组件中 oListSelector 对象的引用。

任务 3:实现列表控制器

在本任务中,您将实现 List 视图的控制器以用于导航。

步骤

  1. 打开 *Carrier.controller.js* 文件。定义该控制器应派生自 BaseController。移除用于全屏导航的两个函数。

  2. 打开项目 controller 文件夹中的 *Carrier.controller.js* 文件。

  3. 按如下方式更新代码:

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", {
});
});
  1. 实现一个 _navigateToCarrierDetails 函数。该函数接受两个参数。将第一个参数命名为 sCarrierId,第二个命名为 bReplace。该函数应导航到 carrierdetails 路由,并传入一个 sCarrierId 值作为参数。

  2. 添加以下代码:

_navigateToCarrierDetails : function(sCarrierId,bReplace) {
this.getRouter().navTo("flights", {
carrid: sCarrierId
}, bReplace);
},
  1. 添加一个 _showDetail 函数。该函数接受一个参数。将参数命名为 oItem。该变量在运行时会包含用户从主列表中选择的项。从 oItem 中读取属性 Carrid 并将其存储在变量 sCarrierId 中。同时检查应用程序是否运行在移动设备上。将结果存储在局部变量 bReplace 中。调用 _navigateToCarrierDetails 函数,并将这两个变量作为参数传入。

  2. _navigateToCarrierDetails 实现之后,按如下方式添加带 oItem 参数的 _showDetail 函数:

_showDetail: function(oItem) {
},
  1. 检查应用程序是运行在移动设备上还是台式机上,并将结果存储在局部变量中。然后从 oItem 对象的 bindingContext 中读取 Carrid 属性,并将其存储在一个变量中。将这两个变量传递给 _navigateToCarrierDetails 函数。将以下代码添加到 _showDetail 函数中:

var sCarrierId = oItem.getBindingContext().getProperty("Carrid");
this._navigateToCarrierDetails(sCarrierId,true);
  1. 实现列表 select 事件的事件处理器。用于处理该事件的赋值函数名为 onSelect。调用 _showDetail 方法并将所选项传递给该函数。表格的选择行为取决于应用程序是否运行在移动设备上。

  2. _showDetail 实现之后添加以下代码:

onSelect: function(oEvent) {
this._showDetail(oEvent.getParameter("listItem") || oEvent.getSource());
},
  1. 实现一个 onBypassed 函数。该函数应处理路由器绕过目标的情况。在该函数内部,对列表对象调用 removeSelections 函数。

  2. onSelect 实现之后添加以下代码:

onBypassed: function() {
this._oList.removeSelections(true);
},
  1. 向实现中添加一个 _onListMatched 函数。该函数不接受参数。当路由器调用 list 路由时,该函数将被调用。对 promise oWhenListLoadingIsDone 被兑现的情况作出反应。如果该 promise 被兑现,则应在明细视图内显示主列表中第一个项的详细信息。

  2. onBypassed 实现之后添加以下代码:

_onListMatched: function() {
this.getListSelector().oWhenListLoadingIsDone.then(
function(mParams) {
var sObjectId = mParams.oFirstListItem.getBindingContext().getProperty("Carrid");
this._navigateToCarrierDetails(sObjectId,true);
}.bind(this)
);
}
  1. 实现 onInit 函数。获取对您视图中主列表的引用,并将该引用存储在名为 oList 的变量中。将 oList 的引用赋给名为 _oList 的成员变量。使用 addEventDelegate-function 为视图的 onBeforeFirstShow 添加一个事件处理器,并调用 ListSelectorsetBoundMasterList,将 oList 对象的引用传递给该函数。实现当路由器调用 masterlist 路由时的行为。如果是这种情况,应调用 _onListMatched 函数。实现当路由器绕过 list 路由时的行为,在该事件发生时注册 onBypassed 函数。然后保存您的更改。

  2. 在开头添加以下代码:

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);
},
  1. 保存您的更改。

任务 4:实现明细控制器

在本任务中,您将实现 Detail 视图的控制器以用于导航。

步骤

  1. 打开 *Flights.controller.js* 文件。定义该控制器应派生自 BaseController。由于我们使用基础控制器的 getrouter 和 onNavBack 函数,您现在可以移除这些函数。

  2. 打开项目 controller 文件夹中的 *Flights.controller.js* 文件。

  3. 按如下方式更新代码:

sap.ui.define([
"student/com/sap/training/advancedsapui5/listdetail/controller/BaseController"
],
function (Controller) {
  1. 移除 getrouter 函数。

  2. 移除 onNavBack 函数。

  3. 修改 _onBindingChange 函数。如果视图未绑定到某个实体,则应清除主列表上的选择。要清除选择,请在 ListSelector 上调用函数 clearListSelection。如果视图绑定到某个实体,则获取该实体的绑定路径,并调用 ListSelector 中的 selectAListItem 函数。将绑定路径作为参数传入。

  4. 您的代码应如下所示:

_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);
}
  1. 修改名为 _onObjectMatched 的函数。将 mainView Model 的 layout 属性改为 TwoColumnsMidExpanded,以调整 sap.f.flexibleColumnLayout 的布局行为。从 bindElement 调用中移除 dataRequesteddataReceived 事件。视图的 Busy 属性必须以不同方式管理,因为每次选择更改时不会都调用 OData 服务。

  2. 您的代码现在应如下所示::

_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)
}
});
},
  1. 修改 onInit 函数。获取 OData Model 引用以附加 RequestSent 和 RequestCompleted 事件,并设置视图的 Busy 属性。

  2. 按如下方式更新 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);
},
  1. 保存您的更改。

任务 5:测试您的应用程序

步骤

  1. 测试您的应用程序。选择一个承运人以查看相应的航线。 生成的应用程序屏幕。左侧显示承运人列表,右侧显示所选承运人的航线。

  2. 按先前练习中所示执行此步骤。

  3. 尝试导航到不存在对应路由的哈希值。应显示 NotFound 视图。 生成的应用程序屏幕。右侧明细窗格显示资源未找到页面。

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