Step 10: Implement “Lazy Loading”
By avoiding load invisible resources to enhance and reflect the speed of the browser, we call this mode as delayed loading (lazy loading).
Modify 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>
To illustrate the temporary load, we achieved only when the user IconTabBar: when selecting the appropriate tab for the two tag Hobbies and Notes loaded content. It is a set id for each IconTabFilter, in order to configure them in the route after.
In resume view, we have removed the content Hobbies and Notes tab, and then we will fill them with dynamic navigation feature.
Creating ResumeHobbies.view.xml
<mvc:View xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc"> <Text text="{Hobbies}"/> </mvc:View>
Creating ResumeNotes.view.xml
<mvc:View xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc"> <Text text="{Notes}"/> </mvc:View>
Modify 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*/); } }, ... }); });
When the selected tab is Hobbies or Notes, we went to look for the corresponding target by routing, and he displayed on the page. When you select the tab is not Hobbies or Notes, will not trigger routing, so do not read resource page can not be displayed.
Here is a valid target resumeTabHobbies and resumeTabNotes, we need to target in two additional routes.
Modify 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" } } } } }
Added two target, resumeTabHobbies and resumeTabNotes, which will cover the configuration information default configuration information in the config.
target resumeTabHobbies parent property is employeeResume. Attribute value of the parent, is another target value, in this case, ensuring that the resumeTabHobbies display, employeeResume will be loaded. This can be seen as a dependency between views.
By providing controlId and controlAggregation properties, the view router ResumeHobbies into Aggregation IconTabFilter ID hobbiesTab of control.
Each target, only the definition of a parent. controlId property to contriol parent view, the parent view especially where the view target set.
So that we achieve a lazy loading, only load the first time it is clicked.
Step 11: Assign Multiple Targets
Use a routing multiple targets, press the button will open a new page contains two sections.
Modify 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>
Add a new button, when the button is clicked, trigger onNavToEmployeeOverview.
Modify 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"); } }); });
Modify 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" } } } } }
Add a new route employeeOverview, using an array refer to two target: employeeOverviewTop and employeeOverviewContent.
target employeeOverviewTop and employeeOverviewContent will target employeeOverview as a parent reference, because we want to put them all in the parent. We also set up viewPath.
When the router to ensure that the corresponding matching target route and displays, in addition to the target view, but also to load the parent view.
New 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>
New 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", {
});
});
New EmployeeOverviewTop.view.xml
<mvc:View xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc" class="sapUiMediumMarginBottom"> <Title text="{i18n>EmployeeOverviewTop}"/> </mvc:View>
New 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 allow a search in a table, Button adjust the order to open a dialog.
New 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); } }); });
Modify i18n.properties
EmployeeOverview=Employee Overview
ShowEmployeeOverview=Show Employee Overview
EmployeeOverviewTop=Employee Overview Top
Region=Region
EmployeeID=Employee ID
Phone=Phone
Employees=Employees
Step 12: Make a Search Bookmarkable