Step 10: Implement “Lazy Loading”
通过避免加载不可见的资源来提高浏览器的反映速度,我们称这种方式为延迟加载(lazy loading)。
修改Resume.view.xml
<mvc:View controllerName="sap.ui.demo.nav.controller.employee.Resume" xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc"> <Page title="{i18n>ResumeOf} {FirstName} {LastName}" id="employeeResumePage" showNavButton="true" navButtonPress=".onNavBack"> <content> <IconTabBar id="iconTabBar" headerBackgroundDesign="Transparent" class="sapUiResponsiveContentPadding" binding="{Resume}" select=".onTabSelect" selectedKey="{view>/selectedTabKey}"> <items> <IconTabFilter id="infoTab" text="{i18n>tabInfo}" key="Info"> <Text text="{Information}"/> </IconTabFilter> <IconTabFilter id="projectsTab" text="{i18n>Projects}" key="Projects"> <mvc:XMLView viewName="sap.ui.demo.nav.view.employee.ResumeProjects"></mvc:XMLView> </IconTabFilter> <IconTabFilter id="hobbiesTab" text="{i18n>Hobbies}" key="Hobbies"> <!-- place content via lazy loading --> </IconTabFilter> <IconTabFilter id="notesTab" text="{i18n>Notes}" key="Notes"> <!-- place content via lazy loading --> </IconTabFilter> </items> </IconTabBar> </content> </Page> </mvc:View>
为了说明临时加载,我们实现了只有当用户在IconTabBar: Hobbies和Notes中为两个tag选择相应的tab时才加载内容。为每个IconTabFilter都是设置了一个id,以便在之后的路由配置他们。
在resume view中,我们删除了Hobbies和Notes tab的内容,之后我们将用导航特性动态填充它们。
创建ResumeHobbies.view.xml
<mvc:View xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc"> <Text text="{Hobbies}"/> </mvc:View>
创建ResumeNotes.view.xml
<mvc:View xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc"> <Text text="{Notes}"/> </mvc:View>
修改Resume.controller.js
sap.ui.define([ "sap/ui/demo/nav/controller/BaseController", "sap/ui/model/json/JSONModel" ], function (BaseController, JSONModel) { "use strict"; var _aValidTabKeys = ["Info", "Projects", "Hobbies", "Notes"]; return BaseController.extend("sap.ui.demo.nav.controller.employee.Resume", { ... _onRouteMatched : function (oEvent) { var oArgs, oView, oQuery; oArgs = oEvent.getParameter("arguments"); oView = this.getView(); oView.bindElement({ ... }); oQuery = oArgs["?query"]; if (oQuery && _aValidTabKeys.indexOf(oQuery.tab) > -1){ oView.getModel("view").setProperty("/selectedTabKey", oQuery.tab); // support lazy loading for the hobbies and notes tab if (oQuery.tab === "Hobbies" || oQuery.tab === "Notes"){ // the target is either "resumeTabHobbies" or "resumeTabNotes" this.getRouter().getTargets().display("resumeTab" + oQuery.tab); } } else { // the default query param should be visible at all time this.getRouter().navTo("employeeResume", { employeeId : oArgs.employeeId, query: { tab : _aValidTabKeys[0] } },true /*no history*/); } }, ... }); });
当所选择的tab是Hobbies或Notes的时候,我们通过路由去寻找相应的target,并将他显示在页面。当选择的tab不是Hobbies或Notes时,不会触发路由,所以不用读取页面不会被显示的资源。
这里有效的target是resumeTabHobbies和resumeTabNotes,我们需要在路由中追加这两个target。
修改manifest.json
{ "_version": "1.12.0", "sap.app": { ... }, "sap.ui": { ... }, "sap.ui5": { ... "routing": { "config": { "routerClass": "sap.m.routing.Router", "viewType": "XML", "viewPath": "sap.ui.demo.nav.view", "controlId": "app", "controlAggregation": "pages", "transition": "slide", "bypassed": { "target": "notFound" } }, "routes": [{ ... }, { "pattern": "employees/{employeeId}/resume:?query:", "name": "employeeResume", "target": "employeeResume" }], "targets": { ... "employeeResume": { "viewId": "resume", "viewName": "employee.Resume", "viewLevel" : 4, "transition": "flip" }, "resumeTabHobbies": { "viewId": "resumeHobbies", "parent": "employeeResume", "viewPath": "sap.ui.demo.nav.view.employee", "viewName": "ResumeHobbies", "controlId": "hobbiesTab", "controlAggregation": "content" }, "resumeTabNotes": { "viewId": "resumeNotes", "parent": "employeeResume", "viewPath": "sap.ui.demo.nav.view.employee", "viewName": "ResumeNotes", "controlId": "notesTab", "controlAggregation": "content" } } } } }
添加了两个target,resumeTabHobbies和resumeTabNotes,其中的配置信息会覆盖默认的config中的配置信息。
target resumeTabHobbies的parent属性为employeeResume。parent的属性值,是另外一个target值,在这个例子中,确保在resumeTabHobbies显示之前,employeeResume会被加载。这可以看作是一个视图之间的依赖。
通过设置controlId和controlAggregation属性,路由器将视图ResumeHobbies放入ID hobbiesTab的IconTabFilter控件的Aggregation中。
每个target,只能定义一个parent。controlId属性设置为parent view的contriol,这里的parent view 特指target中设置的view。
这样我们就实现了延迟加载,只在第一次被点击的时候进行加载。
Step 11: Assign Multiple Targets
使用一个路由的多个目标,按下按钮会,打开一个包含两个部分的新页面。
修改Home.view.xml
<mvc:View controllerName="sap.ui.demo.nav.controller.Home" xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc"> <Page title="{i18n>homePageTitle}" class="sapUiResponsiveContentPadding"> <content> <Button id="displayNotFoundBtn" text="{i18n>DisplayNotFound}" press=".onDisplayNotFound" class="sapUiTinyMarginEnd"/> <Button id="employeeListBtn" text="{i18n>ShowEmployeeList}" press=".onNavToEmployees" class="sapUiTinyMarginEnd"/> <Button id="employeeOverviewBtn" text="{i18n>ShowEmployeeOverview}" press=".onNavToEmployeeOverview" class="sapUiTinyMarginEnd"/> </content> </Page> </mvc:View>
添加一个新的按钮,当按钮被点击时,触发onNavToEmployeeOverview。
修改Home.controller.js
sap.ui.define([ "sap/ui/demo/nav/controller/BaseController" ], function (BaseController) { "use strict"; return BaseController.extend("sap.ui.demo.nav.controller.Home", { ... onNavToEmployees : function () { this.getRouter().navTo("employeeList"); }, onNavToEmployeeOverview : function () { this.getRouter().navTo("employeeOverview"); } }); });
修改manifest.json
{ "_version": "1.12.0", "sap.app": { ... }, "sap.ui": { ... }, "sap.ui5": { ... "routing": { "config": { "routerClass": "sap.m.routing.Router", "viewType": "XML", "viewPath": "sap.ui.demo.nav.view", "controlId": "app", "controlAggregation": "pages", "transition": "slide", "bypassed": { "target": "notFound" } }, "routes": [{ "pattern": "", "name": "appHome", "target": "home" }, { "pattern": "employees", "name": "employeeList", "target": "employees" }, { "pattern": "employees/overview", "name": "employeeOverview", "target": ["employeeOverviewTop", "employeeOverviewContent"] }, { "pattern": "employees/{employeeId}", "name": "employee", "target": "employee" }, { "pattern": "employees/{employeeId}/resume:?query:", "name": "employeeResume", "target": "employeeResume" }], "targets": { ... "resumeTabNotes": { "viewId": "resumeNotes", "parent": "employeeResume", "viewPath": "sap.ui.demo.nav.view.employee", "viewName": "ResumeNotes", "controlId": "notesTab", "controlAggregation": "content" }, "employeeOverview": { "viewId": "employeeOverview", "viewPath": "sap.ui.demo.nav.view.employee.overview", "viewName": "EmployeeOverview", "viewLevel" : 2 }, "employeeOverviewTop": { "viewId": "employeeOverviewTop", "parent": "employeeOverview", "viewPath": "sap.ui.demo.nav.view.employee.overview", "viewName": "EmployeeOverviewTop", "controlId": "EmployeeOverviewParent", "controlAggregation": "content" }, "employeeOverviewContent": { "viewId": "employeeOverviewContent", "parent": "employeeOverview", "viewPath": "sap.ui.demo.nav.view.employee.overview", "viewName": "EmployeeOverviewContent", "controlId": "EmployeeOverviewParent", "controlAggregation": "content" } } } } }
添加了新的路由employeeOverview,使用数组同时引用两个target:employeeOverviewTop和employeeOverviewContent。
target employeeOverviewTop和employeeOverviewContent都将target employeeOverview作为parent 引用,因为我们希望将它们都放在parent中。我们还设置了viewPath。
路由器确保在匹配了相应的路由并显示了目标时,除了目标视图外,还加载了父视图。
新建EmployeeOverview.view.xml
<mvc:View
controllerName="sap.ui.demo.nav.controller.employee.overview.EmployeeOverview"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Page id="EmployeeOverviewParent" title="{i18n>EmployeeOverview}"
showNavButton="true"
navButtonPress=".onNavBack"
class="sapUiResponsiveContentPadding">
<content>
<!-- inserted by routing -->
</content>
</Page>
</mvc:View>
新建EmployeeOverview.controller.js
sap.ui.define([
"sap/ui/demo/nav/controller/BaseController"
], function (BaseController) {
"use strict";
return BaseController.extend("sap.ui.demo.nav.controller.employee.overview.EmployeeOverview", {
});
});
新建EmployeeOverviewTop.view.xml
<mvc:View xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc" class="sapUiMediumMarginBottom"> <Title text="{i18n>EmployeeOverviewTop}"/> </mvc:View>
新建EmployeeOverviewContent.view.xml
<mvc:View controllerName="sap.ui.demo.nav.controller.employee.overview.EmployeeOverviewContent" xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc"> <Table id="employeesTable" items="{/Employees}"> <headerToolbar> <Toolbar> <Title text="{i18n>Employees}" level="H2"/> <ToolbarSpacer /> <SearchField id="searchField" search=".onSearchEmployeesTable" width="50%"/> <Button icon="sap-icon://sort" press=".onSortButtonPressed"/> </Toolbar> </headerToolbar> <columns> <Column id="employeeIDCol"><Text text="{i18n>EmployeeID}"/></Column> <Column id="firstNameCol" demandPopin="true"><Text text="{i18n>FirstName}"/></Column> <Column id="lastNameCol" demandPopin="true"><Text text="{i18n>LastName}"/></Column> <Column id="addressCol" minScreenWidth="Tablet" demandPopin="true"><Text text="{i18n>Address}"/></Column> <Column id="cityCol" minScreenWidth="Tablet" demandPopin="true"><Text text="{i18n>City}"/></Column> <Column id="regionCol" minScreenWidth="Tablet" demandPopin="true"><Text text="{i18n>Region}"/></Column> <Column id="postalCodeCol" minScreenWidth="Tablet" demandPopin="true"><Text text="{i18n>PostalCode}"/></Column> <Column id="countryCol" minScreenWidth="Tablet" demandPopin="true"><Text text="{i18n>Country}"/></Column> <Column id="homePhoneCol" minScreenWidth="Tablet" demandPopin="true" hAlign="Right"><Text text="{i18n>Phone}"/></Column> </columns> <items> <ColumnListItem> <cells> <Text text="{EmployeeID}"/> <Text text="{FirstName}"/> <Text text="{LastName}"/> <Text text="{Address}"/> <Text text="{City}"/> <Text text="{Region}"/> <Text text="{PostalCode}"/> <Text text="{Country}"/> <Text text="{HomePhone}"/> </cells> </ColumnListItem> </items> </Table> </mvc:View>
SearchField允许在表中搜索,Button可以打开一个对话框调整顺序。
新建EmployeeOverviewContent.controller.js
sap.ui.define([ "sap/ui/demo/nav/controller/BaseController", "sap/ui/model/Filter", "sap/ui/model/FilterOperator", "sap/ui/model/Sorter", "sap/m/ViewSettingsDialog", "sap/m/ViewSettingsItem" ], function( BaseController, Filter, FilterOperator, Sorter, ViewSettingsDialog, ViewSettingsItem ) { "use strict"; return BaseController.extend("sap.ui.demo.nav.controller.employee.overview.EmployeeOverviewContent", { onInit: function () { this._oTable = this.byId("employeesTable"); this._oVSD = null; this._sSortField = null; this._bSortDescending = false; this._aValidSortFields = ["EmployeeID", "FirstName", "LastName"]; this._sSearchQuery = null; this._initViewSettingsDialog(); }, onSortButtonPressed : function () { this._oVSD.open(); }, onSearchEmployeesTable : function (oEvent) { this._applySearchFilter( oEvent.getSource().getValue() ); }, _initViewSettingsDialog : function () { this._oVSD = new ViewSettingsDialog("vsd", { confirm: function (oEvent) { var oSortItem = oEvent.getParameter("sortItem"); this._applySorter(oSortItem.getKey(), oEvent.getParameter("sortDescending")); }.bind(this) }); // init sorting (with simple sorters as custom data for all fields) this._oVSD.addSortItem(new ViewSettingsItem({ key: "EmployeeID", text: "Employee ID", selected: true // by default the MockData is sorted by EmployeeID })); this._oVSD.addSortItem(new ViewSettingsItem({ key: "FirstName", text: "First Name", selected: false })); this._oVSD.addSortItem(new ViewSettingsItem({ key: "LastName", text: "Last Name", selected: false })); }, _applySearchFilter : function (sSearchQuery) { var aFilters, oFilter, oBinding; // first check if we already have this search value if (this._sSearchQuery === sSearchQuery) { return; } this._sSearchQuery = sSearchQuery; this.byId("searchField").setValue(sSearchQuery); // add filters for search aFilters = []; if (sSearchQuery && sSearchQuery.length > 0) { aFilters.push(new Filter("FirstName", FilterOperator.Contains, sSearchQuery)); aFilters.push(new Filter("LastName", FilterOperator.Contains, sSearchQuery)); oFilter = new Filter({ filters: aFilters, and: false }); // OR filter } else { oFilter = null; } // update list binding oBinding = this._oTable.getBinding("items"); oBinding.filter(oFilter, "Application"); }, /** * Applies sorting on our table control. * @param {string} sSortField the name of the field used for sorting * @param {string} sortDescending true or false as a string or boolean value to specify a descending sorting * @private */ _applySorter : function (sSortField, sortDescending){ var bSortDescending, oBinding, oSorter; // only continue if we have a valid sort field if (sSortField && this._aValidSortFields.indexOf(sSortField) > -1) { // convert the sort order to a boolean value if (typeof sortDescending === "string") { bSortDescending = sortDescending === "true"; } else if (typeof sortDescending === "boolean") { bSortDescending = sortDescending; } else { bSortDescending = false; } // sort only if the sorter has changed if (this._sSortField && this._sSortField === sSortField && this._bSortDescending === bSortDescending) { return; } this._sSortField = sSortField; this._bSortDescending = bSortDescending; oSorter = new Sorter(sSortField, bSortDescending); // sync with View Settings Dialog this._syncViewSettingsDialogSorter(sSortField, bSortDescending); oBinding = this._oTable.getBinding("items"); oBinding.sort(oSorter); } }, _syncViewSettingsDialogSorter : function (sSortField, bSortDescending) { // the possible keys are: "EmployeeID" | "FirstName" | "LastName" // Note: no input validation is implemented here this._oVSD.setSelectedSortItem(sSortField); this._oVSD.setSortDescending(bSortDescending); } }); });
修改i18n.properties
EmployeeOverview=Employee Overview
ShowEmployeeOverview=Show Employee Overview
EmployeeOverviewTop=Employee Overview Top
Region=Region
EmployeeID=Employee ID
Phone=Phone
Employees=Employees