Business Example
In this exercise, you learn how to implement a List-detail Application with SAPUI5. You also learn how to handle the processing of asynchronous service requests in the UI using JavaScript promises.
Prerequisites
Before you start this exercise, you should have downloaded the prepared files from Github: https://github.com/SAP-samples/sapui5-development-advanced-learning-journey. You should have already uploaded the metadata.xml
file to a new /home/user/projects/Training files/
folder as shown in the previous exercise: Create a SAPUI5 Application Project.
SAP Business Technology Platform and its tools are cloud services. Due to the nature of cloud software, step procedures and naming of fields and buttons may differ from the exercise solution.
Task 1: Create the Basic Components of the List-Detail Application
Steps
Log into your training SAP Business Application Studio.
Perform this step as shown in a previous exercise.
Create a new SAP Fiori Freestyle project using the following information:
Select Template and Target Location
Field Value Template SAP Fiori application Template Selection
Field Value Template Type SAP Fiori Template Basic Datasource and Service Selection
Field Value Data source Upload a Metadata Document Metadata file path /home/user/projects/Training files/metadata.xml View name
Field Value View name List Project Attributes
Field Value Module name ux402_listdetail Application title List-detail App Application namespace student##.com.sap.training.ux402.listdetail Description A simple list-detail app. Project folder path /home/user/projects Minimum SAPUI5 version Let it on the default value Choose File → New Project from Template in the SAP Business Application Studio.
Choose SAP Fiori application and select Start.Â
Choose SAP Fiorias Template Type.
Choose Basic and select Next.
Choose the Data Source and Service Selection following Options:
- As Data source, select Upload a Metadata Document.
- As Metadata file path, enter
/home/user/projects/Training files/metadata.xml
or select the file in the search field and choose OK.
Choose Next to continue.
In the View name field, enter List, and then choose Next.
For the Project Attributes, enter the following values:
- As Module name, enter ux402_listdetail.
- As Application title, enter List-detail Application.
- As Application namespace, enter student##.com.sap.training.ux402.listdetail.
- As Description, enter List-detail Application.
- As Project folder path, enter /home/user/projects.
- Do not change anything for Minimum SAPUI5 version.
- Let all radio buttons selected on No.
Select Finish.
Upload the JSON files for mock data into a new
webapp/localService/data
folder.In the context menu of the
webapp/localService
folder, select New folder....A new line appears where you can enter the new folder name: data. Press Enter to confirm.
In the context menu of the
webapp/localService/data
folder, select Upload....Select the three mockdata JSON files and click Open.
Add the listed views of type XML in the
view
folder and the corresponding controllers of type JavaScript in thecontroller
folder of your project. Use Copy and then Paste in the context menu of theList.view.xml
and theList.controller.js
files to create the files. Rename them to the listed view and controller file names. In the created view files, change thecontrollerName
attribute. In the created controller files, change the controller name in theextend
function.View and Controller file Names
View file name Controller File Name Detail.view.xml Detail.controller.js DetailObjectNotFound.view.xml DetailObjectNotFound.controller.js NotFound.view.xml NotFound.controller.js Select the
List.view.xml
file in the view folder of your project and from the context menu, choose Copy.Select the view folder of your project and from the context menu, choose Paste.
Select the created
List.view_copy.xml
file and from the context menu, choose Rename. ReplaceList.view_copy.xml
with Detail.view.xml and press the Enter key (on your keyboard).In the opened
Detail.view.xml
file, edit thecontrollerName
attribute and replaceList
withDetail
. Save your changes.Select the
List.controller.js
file in the controller folder of your project and from the context menu, choose Copy.Select the controller folder of your project, and from the context menu, choose Paste.
Select the created
List.controller_copy.js
file and from the context menu, choose Rename. ReplaceList.controller_copy.xml
with Detail.controller.js and press the Enter key (on your keyboard).In the opened
Detail.controller.js
file, change the controller name in theextend
function by replacingList
withDetail
. Save your changes.Repeat steps a to f and create the DetailObjectNotFound and NotFound views with the corresponding controllers.
Open the Application main view and add an
sap.f.FlexibleColumnLayout
element inside theApp
element with the listed properties and values:Attribute Value id layout layout {mainView>/layout} backgroundDesign Translucent Open the
App.view.xml
file in the view folder of your project.Add the
sap.f
namespace with the prefixf
to theApp.view.xml
file. Add following code afterxmlns="sap.m"
:Add an
sap.f.FlexibleColumnLayout
element inside theApp
element with the given attribute-value pairs :Code snippetExpand<f:FlexibleColumnLayout id="layout" layout="{mainView>/layout}" backgroundDesign="Translucent"> </f:FlexibleColumnLayout>
Save your changes. Your code should now look like the following:
Code snippetExpand<mvc:View controllerName="student00.com.sap.training.ux402.listdetail.ux402listdetail.controller.App" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:mvc="sap.ui.core.mvc" displayBlock="true" xmlns="sap.m" xmlns:f="sap.f"> <App id="app"> <f:FlexibleColumnLayout id="layout" layout="{mainView>/layout}" backgroundDesign="Translucent"> </f:FlexibleColumnLayout> </App> </mvc:View>
Task 2: Prepare the Internationalized Texts for the Application
Steps
Change the listed key-value pairs in the
i18n.properties
file of your project.Title and Description key-value pairs within the i18n.properties File
Key Value title Exercise: List-Detail App appTitle Exercise: List-Detail App appDescription A simple list-detail app Perform this step as shown in a previous exercise.
For the List View, add the listed key-value pair to the
i18n.properties
file of your project.List View Key-value Pair within the i18n.properties File
Key Value listTitle Carriers Perform this step as shown in a previous exercise.
For the Detail View, add the listed key-value pairs to the
i18n.properties
file of your project.Detail View key-value pairs within the i18n.properties File
Key Value detailTitle Carrier currencyTitle Currency urlTitle Url tableHeaderText Connections tableNoDataText No connections available idColumnText Number cityFromColumnText From cityToColumnText To Perform this step as shown in a previous exercise.
For the NotFound View, add the listed key-value pairs to the
i18n.properties
file of your project.NotFound View Key-value Pairs within the i18n.properties File
Key Value notFoundTitle Not Found notFoundText The requested resource was not found Perform this step as shown in a previous exercise.
For the DetailObjectNotFound View, add the listed key-value pair to the
i18n.properties
file of your project.DetailObjectNotFound View Key-value Pairs within the i18n.properties File
Key Value noObjectFoundText This Carrier is not available Perform this step as shown in a previous exercise.
Task 3: Check and Configure the Routing
Steps
Check the generated routing configuration and make sure that the following routing configuration parameters are configured correctly. Additionally, remove the
clearControlAggregation
configuration parameter.Routing Configuration
Configuration Parameter Values routerClass sap.m.routing.Router viewType XML async true viewPath student##.com.sap.train ing.ux402.listdetail. ux402listdetail.view controlAggregation beginColumnPages controlId layout Open the
manifest.json
file.Locate the routing configuration
config
.Make sure that the listed configuration parameters are included.
Remove the
clearControlAggregation
configuration parameter.Your implementation should look like the following:
Code snippetExpand"config": { "routerClass": "sap.m.routing.Router", "viewType": "XML", "async": true, "viewPath": "student00.com.sap.training.ux402.listdetail.ux402listdetail.view", "controlAggregation": "beginColumnPages", "controlId": "layout" },
Check the
TargetList
target against the listed attributes and values and rename it to masterlist.List Target
Attribute Value viewType XML transition slide viewName List viewLevel 1 viewId list controlAggregation beginColumnPages Find the
Targetlist
target and check to ensure that it has all the listed attributes and values and rename it tomasterlist
.Your implementation should look like the following code:
Code snippetExpand"targets": { "masterlist": { "viewType": "XML", "transition": "slide", "ControlAggregation": "beginColumnPages", "viewId": "list", "viewName": "List", "viewLevel": 1 }
Define a target with the name
carrierdetails
with the listed attributes and values.carrierdetails Target
Configuration Parameter Value viewType XML transition slide viewName Detail viewLevel 2 viewId carrierdetails controlAggregation midColumnPages Add the
carrierdetails
target with the listed attributes and values:Code snippetExpand"carrierdetails": { "viewType": "XML", "transition": "slide", "viewLevel": 2, "controlAggregation": "midColumnPages", "viewName": "Detail", "viewId": "carrierdetails" }
Delete the
RouteList
route. Then define a default route with the namemasterlist
. Assign the route to themasterlist
andcarrierdetails
targets. In addition, setgreedy
tofalse
and set thetitleTarget
to an empty string.Replace the
RouteList
definition with the following code:Code snippetExpand"routes": [ { "name": "masterlist", "pattern": "", "titleTarget": "", "greedy": false, "target": [ "masterlist" ] } ]
Create a new route with the name
carrierdetails
and assign themasterlist
andcarrierdetails
targets to the route. Add the patternCarriers/{objectId}
to that route. In addition, setgreedy
tofalse
and set thetitleTarget
to an empty string.Add the following code after the
masterlist
route definition:Code snippetExpand, { "name": "carrierdetails", "pattern": "Carriers/{objectId}", "titleTarget": "", "greedy": false, "target": [ "masterlist", "carrierdetails" ] }
Define a target with the name
notFound
with the listed attributes and values.notFound target
Configuration Parameter Value viewType XML transition slide controlAggregation midColumnPages clearControlAggregation true viewName NotFound viewId notFound Add the
notFound
target with the listed attributes and values after thecarrierdetails
target definition:Code snippetExpand, "notFound": { "viewType": "XML", "transition":"slide", "controlAggregation": "midColumnPages", "clearControlAggregation": true, "viewName": "NotFound", "viewId": "notFound" }
Define a target with the name
detailObjectNotFound
with the listed attributes and values.detailObjectNotFound target
Configuration Parameter Value viewType XML transition slide controlAggregation midColumnPages clearAggregation true viewName DetailObjectNotFound viewId detailObjectNotFound Add the
detailObjectNotFound
target with the listed attributes and values after thenotFound
target definition:Code snippetExpand, "detailObjectNotFound": { "viewType": "XML", "transition":"slide", "controlAggregation": "midColumnPages", "clearControlAggregation": true, "viewName": "DetailObjectNotFound", "viewId": "detailObjectNotFound" }
Extend the routing configuration in the descriptor by adding a
bypassed
property and setting its target tomasterlist
andnotFound
. Then save your changes.Add the
bypassed
property and set its target tolist
andnotFound
in the routing configuration by adding the following code at the end of theconfig
definition:Your implementation should look like the following code:
Code snippetExpand"routing": { "config": { "routerClass": "sap.m.routing.Router", "viewType": "XML", "async": true, "viewPath": "student00.com.sap.training.ux402.listdetail.ux402listdetail.view", "controlAggregation": "beginColumnPages", "controlId": "layout", "bypassed": { "target": [ "masterlist", "notFound" ] } },
Save your changes.
Check if the
init
function of your component contains the router initialization. If this is not the case, initialize the router of your application.Open the
Component.js
file.Navigate to the
init
function.Ensure that the router is initialized:
Code snippetExpandinit: function () { // call the base component's init function UIComponent.prototype.init.apply(this, arguments); // enable routing this.getRouter().initialize(); // set the device model this.setModel(models.createDeviceModel(), "device"); }
Task 4: Define the List View Content
In this task, you define the list view content for your application. The view will implement a sap.f.DynamicPage
object. The page will show a list of Carriers fetched from the entity set UX_C_Carrier_TP
.
Steps
Open the
List.view.xml
file, remove thepage
element and add the namespacesap.f
with the XML-aliasf
.Update the code as follows:
Code snippetExpand<mvc:View controllerName="student##.com.sap.training.ux402.listdetail.ux402listdetail.controller.List" xmlns:mvc="sap.ui.core.mvc" displayBlock="true" xmlns="sap.m" xmlns:f="sap.f">
Add an
sap.f.DynamicPage
element withsap.f.content-aggregation
to the view with the following properties.Property Value id dynamicPageOverviewId headerExpanded true toggleHeaderOnTitleClick true Add the following code inside the
View
definition:Code snippetExpand<f:DynamicPage id="dynamicPageOverviewId" headerExpanded="true" toggleHeaderOnTitleClick="true"> <f:content> </f:content> </f:DynamicPage>
Create an
sap.m.List
control inside thesap.f.content-Aggregation
and assign the following values to theList
element:Attribute-value Pairs for the sap.m.List ControlÂ
Attribute Value id list items {/UX_C_Carrier_TP} busyIndicatorDelay 0 growing true growingThreshold 20 growingScrollToLoad true mode {= ${device>/system/phone} ? 'None': 'SingleSelectMaster'} selectionChange onSelect  Add the following code inside the
content
aggregation:Code snippetExpand<List id="list" items="{/UX_C_Carrier_TP}" busyIndicatorDelay="0" growing="true" growingThreshold="10" growingScrollToLoad="true" mode="{= ${device>/system/phone} ? 'None' : 'SingleSelectMaster'}" selectionChange="onSelect"> </List>
Add the
items
aggregation to thesap.m.List-control
and add a control type ofsap.m.ObjectListItem
to the items-aggregation with the following attributes:Attribute-value Pairs for the sap.m.ObjectListItem Element
Attribute Value id objectListItem title {Carrname} intro {Carrid} type {= ${device>/system/phone} ? 'Active' : 'Inactive'} press onSelect Extend your code with the following implementation inside the
List
element:Code snippetExpand<items> <ObjectListItem id="objectListItem" title="{Carrname}" intro="{Carrid}" type="{= ${device>/system/phone} ? 'Active' : 'Inactive'}" press="onSelect"> </ObjectListItem> </items>
Your implementation should now look like the following:
Code snippetExpand<mvc:View controllerName="student00.com.sap.training.ux402.listdetail.ux402listdetail.controller.List" xmlns:mvc="sap.ui.core.mvc" displayBlock="true" xmlns="sap.m" xmlns:f="sap.f"> <f:DynamicPage id="dynamicPageOverviewId" headerExpanded="true" toggleHeaderOnTitleClick="true"> <f:content> <List id="list" items="{/UX_C_Carrier_TP}" busyIndicatorDelay="0" growing="true" growingThreshold="10" growingScrollToLoad="true" mode="{= ${device>/system/phone} ? 'None' : 'SingleSelectMaster'}" selectionChange="onSelect"> <items> <ObjectListItem id="objectListItem" title="{Carrname}" intro="{Carrid}" type="{= ${device>/system/phone} ? 'Active' : 'Inactive'}" press="onSelect"> </ObjectListItem> </items> </List> </f:content> </f:DynamicPage> </mvc:View>
Task 5: Define the Detail View content
In this task, you define the detail view content for your application. The view should show the details of the selected carrier using the sap.uxap.ObjectPageDynamicHeaderTitle
element and sap.uxap.headerContent
aggregation. The table that will be implemented shows the available flights for the selected carrier.Â
Steps
Open the
Detail.view.xml
file, remove thepage
element and add the namespacessap.uxap
with the XML-aliasux
andsap.ui.layout
with the XML-aliaslayout
.Update the code as follows:
Code snippetExpand<mvc:View controllerName="student##.com.sap.training.ux402.listdetail.ux402listdetail.controller.Detail" xmlns:mvc="sap.ui.core.mvc" displayBlock="true" xmlns="sap.m" xmlns:ux="sap.uxap" xmlns:layout="sap.ui.layout">
Add an
sap.uxap.ObjectPageLayout
element to the view. Add the attributeid
with the valueobjectPageLayout
.Add the following code inside the
View
definition:
Add an
sap.uxap.headerTitle
,sap.uxap.headerContent
andsap.uxap.sections
aggregation to thesap.uxap.ObjectPageLayout
control.Add the following code inside the
ObjectPageLayout
element:Code snippetExpand<ux:headerTitle> </ux:headerTitle> <ux:headerContent> </ux:headerContent> <ux:sections> </ux:sections>
Add an
sap.uxap.ObjectPageDynamicHeaderTitle
control to theheaderTitle
aggregation. For the attributeid
, add the valueobjectPageDynamicHeader
.Add the following code inside the
headerTitle
element:Code snippetExpand<ux:ObjectPageDynamicHeaderTitle id="objectPageDynamicHeader"> </ux:ObjectPageDynamicHeaderTitle>
Add an
sap.uxap.expandedHeading
aggregation to thesap.uxap.ObjectPageDynamicHeaderTitle
control.Add the following code inside the
ObjectPageDynamicHeaderTitle
element:
Add an
sap.m.Title
control to thesap.uxap.expandedHeading
aggregation with the listed attributes and values.Attribute-value Pairs for the sap.m.ObjectAttribute Elements
Attribute Value id title1 text {Carrname} level H2 Add the following code inside the
expandedHeading
element:The
headerTitle
section should now look like the following:Code snippetExpand<ux:headerTitle> <ux:ObjectPageDynamicHeaderTitle id="objectPageDynamicHeader"> <ux:expandedHeading> <Title id="title1" text="{Carrname}" level="H2"/> </ux:expandedHeading> </ux:ObjectPageDynamicHeaderTitle> </ux:headerTitle>
Add an
sap.m.FlexBox
control to thesap.uxap.headerContent
with the listed attributes and values.Attribute Value id flexbox warp Wrap Add the following code inside the
headerContent
element:Â
Add an
sap.f.Avatar
and asap.ui.layout.VerticalLayout
control to thesap.m.Flex
control with the listed attributes and values.Element Attribute Value 1 id avatar src sap-icon://flight 2 id verticallayout1 class sapUiSmallMarginBeginEnd Add the following code inside the
FlexBox
element:Code snippetExpand<Avatar id="avatar" src="sap-icon://flight"/> <layout:VerticalLayout id="verticalLayout1" class="sapUiSmallMarginBeginEnd"> </layout:VerticalLayout>
Add two
sap.m.Label
controls to thesap.ui.layout.VerticalLayout
control with the listed attributes and values:Element Attribute Value 1 id label1 text {Carrid} 2 id label2 text {Url} Add the following code into the
VerticalLayout
element:Your code for the
headerContent
should now look like the following:Code snippetExpand<ux:headerContent> <FlexBox wrap="Wrap" id="flexBox"> <Avatar id="avatar" src="sap-icon://flight"/> <layout:VerticalLayout id="verticalLayout1" class="sapUiSmallMarginBeginEnd"> <Label id="label1" text="{Carrid}"/> <Label id="label2" text="{Url}"/> </layout:VerticalLayout> </FlexBox> </ux:headerContent>
Add an
sap.uxap.ObjectPageSection
control to thesap.uxap.sections
aggregation. Add the valueobjectPageSection
to the attributeid
.Add the following code inside the
sections
element:
Add an
sap.uxap.ObjectPageSubSection
control to thesap.uxap.ObjectPageSection
control. Add the valueobjectPageSubSection
to the attributeid
.Add the following code inside the
ObjectPageSection
element:
Add an
sap.m.Table
control to thesap.uxap.ObjectPageSubSection
aggregation and add the following attributes to theTable
control:Attribute-value Pairs for the sap.m.Table ElementÂ
Attribute Value id table headerText {i18n>tableHeaderText} items {to_Connection} noDataText {i18n>talbeNoDataText Add the following code inside the
ObjectPageSubSection
element:Code snippetExpand<Table id="table" headerText="{i18n>tableHeaderText}" items="{to_Connection}" noDataText="{i18n>tableNoDataText}"> </Table>
Add the
columns
aggregation to theTable
control and add threesap.m.Column
controls to the columns aggregation. For each control, assign the following attributes:Element Attribute Values 1 id column1 2 id column2 3 id column3 Add the following code inside the
Table
element:Code snippetExpand<columns> <Column id="column1"> </Column> <Column id="column2"> </Column> <Column id="column3"> </Column> </columns>
To each of the
sap.m.Columns
controls, add onesap.m.Text
control and assign the following attributes:Attribute-value Pairs for the sap.m.Text Elements
Element Attribute Value 1 id text1 text {i18n>idColumnText} 2 id text2 text {i18n>cityFromColumnText} 3 id text3 text {i18n>cityToColumnText} Add the following code into the first column definition:
Add the following code into the second column definition:
Add the following code into the third column definition:
Add the
items
aggregation to theTable
element.ÂAdd the following code after the
columns
aggregation:
Add an
sap.m.ColumnListItem
control to theitems
aggregation and add the valuecolumnListItem
to the attributeid
.Add the following code inside the
items
aggregation:
Add the
cells
aggregation to theColumnListItem
control.ÂAdd the following code into the
ColumnListItem
element:
Add three
sap.m.Text
controls to thecells
aggregation and add the following attributes and values to the Text elements.Element Attribute Value 1 id text4 text {Carrid} 2 id text5 text {Cityfrom} 3 id text6 text {Cityto} Add the following code into the
cells
aggregation:Code snippetExpand<Text id="text4" text="{Carrid}"/> <Text id="text5" text="{Cityfrom}"/> <Text id="text6" text="{Cityto}"/>
Your code for the sections aggregation should now look like the following:
Code snippetExpand<ux:sections> <ux:ObjectPageSection id="objectPageSection"> <ux:ObjectPageSubSection id="objectPageSubSection1"> <Table id="table" headerText="{i18n>tableHeaderText}" items="{to_Connection}" noDataText="{i18n>tableNoDataText}"> <columns> <Column id="column1"> <Text id="text1" text="{i18n>idColumnText}"/> </Column> <Column id="column2"> <Text id="text2" text="{i18n>cityFromColumnText}"/> </Column> <Column id="column3"> <Text id="text3" text="{i18n>cityToColumnText}"/> </Column> </columns> <items> <ColumnListItem id="columnListItem"> <cells> <Text id="text4" text="{Carrid}"/> <Text id="text5" text="{Cityfrom}"/> <Text id="text6" text="{Cityto}"/> </cells> </ColumnListItem> </items> </Table> </ux:ObjectPageSubSection> </ux:ObjectPageSection> </ux:sections>
Task 6: Define the NotFound View content
The NotFound view is displayed when the requested target was not found. Define the NotFound view content as a MessagePage
.
Steps
Open the
NotFound.view.xml
file and remove thepage
element.Perform this step as shown in a previous exercise.
Add an
sap.m.MessagePage
element to the view and add the following attributes to the element:Attribute-value Pairs for the sap.m.MessagePage Element
Attribute Value id notFoundMessagePage title {i18n>notFoundTitle} text {i18n>notFoundText} icon sap-icon://document Update the code as follows:
Code snippetExpand<mvc:View controllerName="student##.com.sap.training.ux402.listdetail.ux402listdetail.controller.NotFound" xmlns:mvc="sap.ui.core.mvc" displayBlock="true" xmlns="sap.m"> <MessagePage id="notFoundMessagePage" title="{i18n>notFoundTitle}" text="{i18n>notFoundText}" icon="sap-icon://document" > </MessagePage> </mvc:View>
Task 7: Define the DetailObjectNotFound View content
The DetailObjectNotFound view is displayed when the detail was not found. Define the DetailObjectNotFound view content as a MessagePage
.
Steps
Open the
DetailObjectNotFound.view.xml
file and remove thepage
element.Perform this step as shown in a previous exercise.
Add an
sap.m.MessagePage
element to the view and add the following attributes to the element:Attribute-value Pairs for the sap.m.MessagePage Element
Attribute Value id detailObjectNotFoundPage title {i18n>detailTitle} text {i18n>noObjectFoundText} icon sap-icon://document Update the code as follows:
Code snippetExpand<mvc:View controllerName="student##.com.sap.training.ux402.listdetail.ux402listdetail.controller.DetailObjectNotFound" xmlns:mvc="sap.ui.core.mvc" displayBlock="true" xmlns="sap.m"> <MessagePage id="detailObjectNotFoundPage" title="{i18n>detailTitle}" text="{i18n>noObjectFoundText}" icon="sap-icon://document" > </MessagePage> </mvc:View>
Task 8: Provide View Synchronization Functionality
In this task, you implement view synchronization by creating a class with the name ListSelector. The ListSelector
implementation should be placed in the controller
folder of your project.
Steps
In the controller folder, create a file with name ListSelector.js.
Open the context menu of the controller folder and choose New File.
Enter ListSelector.js and choose OK.
Derive the
ListSelector
fromsap.ui.base.Object
.Update the code as follows:
Code snippetExpandsap.ui.define([ "sap/ui/base/Object" ], function(BaseObject) { "use strict"; return BaseObject.extend("student##.com.sap.training.ux402.listdetail.ux402listdetail.controller.ListSelector", { }); });
Add a
constructor
function.Add a
constructor
function as follows:
Inside the
constructor
function, create aPromise
instance and assign the instance to a member variable called_oWhenListHasBeenSet
. Pass an anonymous function to the Promise-constructor. The function should take an argument with the namefnResolveListHasBeenSet
. Assign this argument to a public variable_fnResolveListHasBeenSet
of function object. Make sure that the reference of the anonymous function points to the right object.Add the
_oWhenListHasBeenSet
variable as follows:Code snippetExpandthis._oWhenListHasBeenSet = new Promise(function (fnResolveListHasBeenSet) { this._fnResolveListHasBeenSet = fnResolveListHasBeenSet; }.bind(this));
Create another
Promise
instance and assign the object to a member variable with the nameoWhenListLoadingIsDone
. The constructor function of the Promise should take an anonymous function with two arguments. Name the first argument fnResolve and the second fnReject. Make sure that the reference inside the promise points to the correct object.Add the
oWhenListLoadingIsDone
variable as follows:Code snippetExpandthis.oWhenListLoadingIsDone = new Promise(function (fnResolve, fnReject) { }.bind(this));
Implement the anonymous function created in previous step in a way that when the
_oWhenListHasBeenSet
promise is resolved, an anonymous function with anoList
parameter is called.Add the following code inside the
Promise
function ofoWhenListLoadingIsDone
:
The
oList
object will contain a reference at runtime to a list of the List view. Register an event handler for thedataReceived
event for theitems
binding. The event handler registered for that event should check whether data are received from the backend during binding or not. When no data received from a backend call thefnReject
function point should be invoked. Pass a literal javascript object with a propertylist
and a propertyerror
. Assign theoList
list object to thelist
property and atrue
value to theerror
property.Add the following code after
.then(function (oList) {
:Code snippetExpandoList.getBinding("items").attachEventOnce("dataReceived", function (oData) { if (!oData.getParameter("data")) { fnReject({ list : oList, error: true }); } }
Continue with the implementation and enhance the event handler for the
dataReceived
event. Check whether the list referenced by theoList
object contains at least one item. If this is the case, call the function point,fnResolve
. Pass a literal javascript object with a propertylist
and a propertyfirstListitem
. Assign theoList
list object to thelist
property and the first item of the list to thefirstListitem
property. If there is not at least one item in the list, callfnReject
. Pass a literal javascript object with a propertylist
and a propertyerror
. Assign theoList
list object to thelist
property and afalse
value to theerror
property.Add the following code after the
if
statement closing bracket :Code snippetExpandvar oFirstListItem = oList.getItems()[0]; if(oFirstListItem) { fnResolve({ list: oList, oFirstListItem: oFirstListItem }) } else { // No items in the list fnReject({ list : oList, error: false }); }
The complete implementation of the constructor function should look like the code in the following figure:
Code snippetExpandconstructor : function () { this._oWhenListHasBeenSet = new Promise(function (fnResolveListHasBeenSet) { this._fnResolveListHasBeenSet = fnResolveListHasBeenSet; }.bind(this)); this.oWhenListLoadingIsDone = new Promise(function (fnResolve, fnReject) { this._oWhenListHasBeenSet .then(function (oList) { oList.getBinding("items").attachEventOnce("dataReceived", function (oData) { if (!oData.getParameter("data")) { fnReject({ list : oList, error: true }); } var oFirstListItem = oList.getItems()[0]; if(oFirstListItem) { fnResolve({ list: oList, oFirstListItem: oFirstListItem }) } else { // No items in the list fnReject({ list : oList, error: false }); } } ); }); }.bind(this)); }
Implement a
setBoundMasterList
function. The function should take one argument namedoList
. Assign theoList
parameter to a member variable named_oList
and call the function pointer stored in_fnResolveListHasBeenSet
. Pass theoList
object to that function pointer.Add the following code after the
constructor
function:Code snippetExpandsetBoundMasterList: function(oList) { this._oList = oList; this._fnResolveListHasBeenSet(oList); },
Implement a function named
selectAListItem
. The function should take one argument namedsBindingPath
. ThesBindingPath
argument will contain the path to an item of the master list. The function should react when theoWhenListLoadingIsDone
promise is resolved and should check whether thesBindingPath
variable points to the same entity of the list that was already selected. If this, or the mode of the list, equalsNone
, the processing should return to the caller. Otherwise, the item of the list should be selected.Implement the code for
selectAListItem
after thesetBoundMasterList
function, as follows:Code snippetExpandselectAListItem : function (sBindingPath) { this.oWhenListLoadingIsDone.then( function () { var oList = this._oList, oSelectedItem; if (oList.getMode() === "None") { return; } oSelectedItem = oList.getSelectedItem(); // skip update if the current selection is already matching the object path if (oSelectedItem && oSelectedItem.getBindingContext().getPath() === sBindingPath) { return; } oList.getItems().some(function (oItem) { if (oItem.getBindingContext() && oItem.getBindingContext().getPath() === sBindingPath) { oList.setSelectedItem(oItem); return true; } }); }.bind(this)); },
Implement a
clearListSelection
function. This function should reset the statuses selected at all items of the list. The deselection should be done when the promise stored in_oWhenListHasBeenSet
is resolved. Then save your changes.Add the
clearListSelection
function as follows:Code snippetExpandclearMasterListSelection: function() { this._oWhenListHasBeenSet.then(function() { this._oList.removeSelections(true); }.bind(this)); }
Save your changes.
To make sure that the
ListSelector
can be referenced from each part of the application, we must create an instance of theListSelector
at a more global level. Therefore, create an instance of theListSelector
inside theComponent.js
file and make sure that the instance is a member variable of the component. Then save your changes.Open the
Component.js
file located in the controller folder of your project.Add a reference to the
ListSelector
in theComponent.js
file as follows:Code snippetExpandsap.ui.define([ "sap/ui/core/UIComponent", "sap/ui/Device", "student##/com/sap/training/ux402/listdetail/ux402listdetail/model/models", "student##/com/sap/training/ux402/listdetail/ux402listdetail/controller/ListSelector" ], function (UIComponent, Device, models, ListSelector) {
In the
init
function of the Component, declare a member variable namedoListSelector
and assign anListSelector
instance to the variable. Then save your changes.You
init
function should now look like this:Code snippetExpandinit: function () { // call the base component's init function UIComponent.prototype.init.apply(this, arguments); // instantiation of the listselector this.oListSelector = new ListSelector(); // enable routing this.getRouter().initialize(); // set the device model this.setModel(models.createDeviceModel(), "device"); }
Task 9: Implement the Base Controller
In this task, you implement a base controller class .This class provides to all controllers some functionalities for reuse (for routing, view synchronization, and backward navigation).
Steps
Add a file named BaseController.js to the controller folder of your project.
Open the context menu of the controller folder and choose New File.
Enter BaseController.js and choose OK.
Derive a new class from
sap.ui.core.mvc.Controller
.Add the following code :
Code snippetExpandsap.ui.define([ "sap/ui/core/mvc/Controller" ], function (Controller) { "use strict"; return Controller.extend("student##.com.sap.training.ux402.listdetail.ux402listdetail.controller.BaseController", { }); });
Add a reference to
sap.ui.core.routing.History
and pass the reference asHistory
to the callback.Update the code as follows:
Code snippetExpandsap.ui.define([ "sap/ui/core/mvc/Controller", "sap/ui/core/routing/History" ], function (Controller, History) {
Implement a function named
getRouter
and return the router of the component.Add the following code inside the
extend
function :
Implement a
getListSelector
function and return the reference of theoListSelector
object from the component.Add the following code after the
getRouter
implementation:
Implement a
getResourceBundle
function and return a reference to the i18n model of the application.Add the following code after the
getListSelector
implementation :Code snippetExpandgetResourceBundle: function () { return this.getOwnerComponent().getModel("i18n").getResourceBundle(); },
Implement a function named
onNavBack
. The function should navigate back to the master list using themasterlist
route. Then save your changes.Add the following code after the
getResourceBundle
implementation:Code snippetExpandonNavBack: function() { var sPreviousHash = History.getInstance().getPreviousHash(); if (sPreviousHash !== undefined) { // The history contains a previous entry history.go(-1); } else { // Otherwise we go backwards with a forward history var bReplace = true; this.getRouter().navTo("masterlist", {}, bReplace); } }
Save your changes. Your
BaseController
implementation should now look like the following:Code snippetExpandsap.ui.define([ "sap/ui/core/mvc/Controller", "sap/ui/core/routing/History" ], function (Controller, History) { "use strict"; return Controller.extend("student00.com.sap.training.ux402.listdetail.ux402listdetail.controller.BaseController", { getRouter: function () { return this.getOwnerComponent().getRouter(); }, getListSelector: function() { return this.getOwnerComponent().oListSelector; }, getResourceBundle: function () { return this.getOwnerComponent().getModel("i18n").getResourceBundle(); }, onNavBack: function() { var sPreviousHash = History.getInstance().getPreviousHash(); if (sPreviousHash !== undefined) { // The history contains a previous entry history.go(-1); } else { // Otherwise we go backwards with a forward history var bReplace = true; this.getRouter().navTo("masterlist", {}, bReplace); } } }); });
Task 10: Implement the Application ControllerÂ
In this task, you implement the controller of the App view to link the JSON model for layout to the Application.
Steps
Open the
App.controller.js
file. Define that the controller should be derived from theBaseController
. Moreover, addsap/ui/model/JSONModel
to the namespace.Open the
App.controller.js
file located in the controller folder of your project.Update the code as follows :
Code snippetExpandsap.ui.define( [ "./BaseController", "sap/ui/model/json/JSONModel" ], function(BaseController, JSONModel) { "use strict"; return BaseController.extend("student00.com.sap.training.ux402.listdetail.ux402listdetail.controller.App", { onInit() { } }); } );
Implement the
onInit
function. Create an JSON-Model with an attribute layout and assign the created Model to the App view.Add the following code into the
onInit
function:Code snippetExpandvar oViewModel = new JSONModel({ layout : "OneColumn" }); this.getView().setModel(oViewModel, "mainView");
Task 11: Implement the List Controller
In this task, you implement the controller of the List view for navigation.
Steps
Open the
List.controller.js
file. Define that the controller should be derived from theBaseController
. Add a reference to theDevice
API of SAPUI5.Open the
List.controller.js
file located in thecontroller
folder of your project.Update the code as follows:
Code snippetExpandsap.ui.define([ "student##/com/sap/training/ux402/listdetail/ux402listdetail/controller/BaseController", "sap/ui/Device" ], /** * @param {typeof sap.ui.core.mvc.Controller} Controller */ function (Controller, Device) { "use strict";
Implement a
_navigateToCarrierDetails
function. The function takes two arguments. Name the first argumentsCarrierId
and the secondbReplace
. The function should navigate to thecarrierdetails
route and pass asCarrierId
value as a parameter.Add the following code after the
onInit
function:Code snippetExpand_navigateToCarrierDetails : function(sCarrierId,bReplace) { this.getRouter().navTo("carrierdetails", { objectId: sCarrierId }, bReplace); },
Add a
_showDetail
function. The function takes one argument. Name the argumentoItem
. This variable will contain during runtime the item selected by the user from the master list. Read the property
from theCarrid oItem
and store it in a variablesCarrierId
. Check also if the app is running on a mobile device or not. Store the result in a local variablebReplace
. Invoke the_navigateToCarrierDetails
function and pass both variables as an argument.After the
_navigateToCarrierDetails
implementation, add the_showDetail
function with anoItem
argument as follows:Check whether the app is running on a mobile device or on a desktop and store the result in a local variable. Then read the
Carrid
property from thebindingContext
of theoItem
object and store it in a variable. Pass both variables to the_navigateToCarrierDetails
function. Add the following code into the_showDetail
function:Code snippetExpandvar bReplace = !Device.system.phone; var sCarrierId = oItem.getBindingContext().getProperty("Carrid"); this._navigateToCarrierDetails(sCarrierId,bReplace);
Implement the event handler for the select event of the list. The assign function to handle the event was called
onSelect
. Invoke the_showDetail
method and pass the selected item to the function. The selection behavior of the table depends on whether the app is running on a mobile device or not.Add the following code after the
_showDetail
implementation:Code snippetExpandonSelect: function(oEvent) { this._showDetail(oEvent.getParameter("listItem") || oEvent.getSource()); },
Implement an
onBypassed
function. This function should handle the case that when the router bypasses the target. Inside the function, invoke theremoveSelections
function on the list object.Add the following code after the
onSelect
implementation:
Add an
_onListMatched
function to the implementation. The function takes no argument. The function will be called when thelist
route is invoked by the router. React on the case when the promiseoWhenListLoadingIsDone
is resolved. If the promise is resolved, the details of the first item from the master list should be displayed inside the details view. This should only be processed when the mode of list is notNone
.Add the following code after the
onBypassed
implementation :Code snippetExpand_onListMatched: function() { this.getListSelector().oWhenListLoadingIsDone.then( function(mParams) { if (mParams.list.getMode() === "None") { return; } var sObjectId = mParams.firstListitem.getBindingContext().getProperty("Carrid"); this._navigateToCarrierDetails(sObjectId,true); }.bind(this) ); }
Implement the
onInit
function. Get a reference to the master list of your view and store the reference in a variable namedoList
. Assign the reference tooList
to a member variable named_oList
. Add a eventhandler foronBeforeFirstShow
of the view usingaddEventDelegate-function
and invoke thesetBoundMasterList
of theListSelector
and pass the reference to theoList
object to the function. Implement a behavior when themasterlist
route is invoked by the router. If this is the case, the_onListMatched
function should be invoked. Implement a behavior when the router bypasses thelist
route, register theonBypassed
function when this event occurs. Then save your changes.Add the following code to the
onInit
function:Code snippetExpandvar oList = this.byId("list"); this._oList = oList; this.getView().addEventDelegate({ onBeforeFirstShow: function () { this.getOwnerComponent().oListSelector.setBoundMasterList(this._oList); }.bind(this) }); this.getRouter().getRoute("masterlist").attachPatternMatched(this._onListMatched, this); this.getRouter().attachBypassed(this.onBypassed, this);
Save your changes.
Task 12: Implement the Detail Controller
In this task, you implement the controller of the Detail view for navigation.
Steps
Open the
Detail.controller.js
file. Define that the controller should be derived from theBaseController
and add a reference to theDevice
API of SAPUI5.Open the
Detail.controller.js
file located in the controller folder of your project.Update the code as follows:
Code snippetExpandsap.ui.define([ "student##/com/sap/training/ux402/listdetail/ux402listdetail/controller/BaseController", "sap/ui/Device" ], /** * @param {typeof sap.ui.core.mvc.Controller} Controller */ function (Controller, Device) { "use strict";
Define a function named
_onBindingChange
. The function should check whether the view is bound to an entity or not. If not, a target with the namedetailObjectNotFound
should be displayed and the selection on the master list should be cleared. To clear the selection, call the functionclearListSelection
on theListSelector
. If the view is bound to an entity, get the binding path of the entity and invoke theselectAListItem
function from theListSelector
. Pass the binding path as an argument.Add the following code after the
onInit
function:Code snippetExpand_onBindingChange: function() { var oView = this.getView(); var oElementBinding = oView.getElementBinding(); if (!oElementBinding.getBoundContext()) { this.getRouter().getTargets().display("detailObjectNotFound"); this.getOwnerComponent().oListSelector.clearMasterListSelection(); return; } var sPath = oElementBinding.getPath(); this.getOwnerComponent().oListSelector.selectAListItem(sPath); },
Implement a function named
_bindView
. The function takes one argument namedsObjectPath
. The variable contains the path to the selected Object from the master list. Update the binding of the view using thebindElement
function. Pass a literal object to thebindElement
function. Add a property named path to the literal object and assign thesObjectPath
variable to the property. Then add anevents
property to the literal object. Assign another literal object to theevents
property. Define three attributes to the events object. The first property is calledchange
. Assign the reference to the function_onBindingChange
to the property. Add a propertydataRequested
anddataReceived
. Assign a function to each property. Implement the function fordataRequested
and show that the view is busy. Implement the function fordataReceived
and hide the busy indicator for the view.Add the following code after the
_onBindingChange
implementation:Code snippetExpand_bindView: function(sObjectPath) { var oView = this.getView(); this.getView().bindElement({ path: sObjectPath, events: { change: this._onBindingChange.bind(this), dataRequested: function() { oView.setBusy(true); }, dataReceived: function() { oView.setBusy(false); } } }); },
Implement a function named
_onObjectMatched
. This function is an event handler function for the Pattern-Matched event during navigation. Read the navigation propertyobjectId
from theevents
arguments and call the_bindView
function of the controller. Pass theobjectId
to the function. In addition, change the layout attribute of the mainView Model toTwoColumnsMidExpanded
to adjust the layout behavior of yoursap.f.flexibleColumnLayout
.Add the following code after the
_bindView
implementation:Code snippetExpand_onObjectMatched: function(oEvent) { this.getView().getModel("mainView").setProperty("/layout", "TwoColumnsMidExpanded"); var sObjectPath = "/UX_C_Carrier_TP('" + oEvent.getParameter("arguments").objectId + "')"; this._bindView(sObjectPath); }
Implement the
onInit
function. Register the_onObjectMatched
event handler for the pattern matching event of thecarrierdetails
route. Then save your changes.Update the code of the
onInit
function as follows:Code snippetExpandonInit: function () { this.getRouter().getRoute("carrierdetails").attachPatternMatched(this._onObjectMatched, this); },
Save your changes.
Task 13: Implement the NotFound Controller
In this task, you implement the controller of the NotFound view.
Steps
Open the
NotFound.controller.js
file. Define that the controller should be derived from theBaseController
. In addition, delete theonInit
function. Then save your changes.Open the
NotFound.controller.js
file located in the controller folder of your project.Update the code as follows:
Code snippetExpandsap.ui.define([ "student##/com/sap/training/ux402/listdetail/ux402listdetail/controller/BaseController" ], /** * @param {typeof sap.ui.core.mvc.Controller} Controller */ function (Controller) { "use strict"; return Controller.extend("student00.com.sap.training.ux402.listdetail.ux402listdetail.controller.NotFound", { }); });
Save your changes.
Task 14: Implement the DetailObjectNotFound Controller
In this task, you implement the controller of the DetailObjectNotFound view.
Steps
Open the
DetailObjectNotFound.controller.js
file. Define that the controller should be derived from theBaseController
. In addition, delete theonInit
function. Then save your changes.Open the
DetailObjectNotFound.controller.js
file located in the controller folder of your project.Update the code as follows:
Code snippetExpandsap.ui.define([ "student##/com/sap/training/ux402/listdetail/ux402listdetail/controller/BaseController" ], /** * @param {typeof sap.ui.core.mvc.Controller} Controller */ function (Controller) { "use strict"; return Controller.extend("student00.com.sap.training.ux402.listdetail.ux402listdetail.controller.DetailObjectNotFound", { }); });
Save your changes.
Task 15: Test your Application
Steps
Test your application. Choose a carrier to see the corresponding connections.
Perform this step as shown in a previous exercise.
Try to navigate to a hash for which no route exists. The NotFound view should be displayed.
Change the URL in your browser to something not corresponding to a route, for example Carrier/AA instead of Carriers/AA.
Try to navigate to a hash for which a route exists, but also to the object key for which no suitable detail object can be loaded. The
DetailObjectNotFound
view should be displayed.Change the URL in your browser so that the Carrier id is not known, for example enter Carriers/XX.