Sie sind auf Seite 1von 36

Building

PDF download from SAP Help Portal:


http://help.sap.com/saphelp_uiaddon10/helpdata/en/b4/d66ebee72645c1a3501a769e935541/content.htm
Created on March 31, 2016

The documentation may have changed since you downloaded the PDF. You can always find the latest information on SAP Help
Portal.

Note
This PDF document contains the selected topic and its subtopics (max. 150) in the selected structure. Subtopics from other structures are not included.

2016 SAP SE or an SAP affiliate company. All rights reserved. No part of this publication may be reproduced or transmitted in any form or for any purpose
without the express permission of SAP SE. The information contained herein may be changed without prior notice. Some software products marketed by SAP
SE and its distributors contain proprietary software components of other software vendors. National product specifications may vary. These materials are
provided by SAP SE and its affiliated companies ("SAP Group") for informational purposes only, without representation or warranty of any kind, and SAP
Group shall not be liable for errors or omissions with respect to the materials. The only warranties for SAP Group products and services are those that are set
forth in the express warranty statements accompanying such products and services, if any. Nothing herein should be construed as constituting an additional
warranty. SAP and other SAP products and services mentioned herein as well as their respective logos are trademarks or registered trademarks of SAP SE in
Germany and other countries. Please see www.sap.com/corporate-en/legal/copyright/index.epx#trademark for additional trademark information and notices.

Table of content

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 1 of 36

Table of content
1 Building
1.1 Step 1: Index
1.2 Step 2: Component
1.3 Step 3: Navigation and Routing
1.4 Step 4: Internationalization
1.5 Step 5: Device Model
1.6 Step 6: Custom Utilities
1.7 Step 7: Model View Controller
1.8 Step 8: Master View
1.9 Step 9: Detail View
1.10 Step 10: Detail XML Fragments
1.11 Step 11: AddProduct View
1.12 Step 12: NotFound View

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 2 of 36

1 Building
We'll take a step-by-step approach and build up the app over a series of small progressions. We'll start out at nothing, and end up with a fully functioning app.
In the previous part "Preparing", we examined the general characteristics and specific aspects of an enterprise scale app. We also looked into design patterns,
and the specific business scenario that we're going to cover. In this part, we're going to build the app.

App Overview and Structure


Let's remind ourselves of what we're aiming for:

The app is made up of a number of moving parts, which are shown in this diagram:

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 3 of 36

Let's have a quick tour of how the app is put together, visiting each file in turn.

Index, Component and Message Bundle


In the beginning the browser loads the web application's index.html which contains only minimal code. It loads the SAPUI5 toolkit core using the standard
variant bootstrap script tag, and there is a single function call to instantiate an sap.ui.core.ComponentContainer and place it in the body of the HTML
document. The sap.ui.core.ComponentContainer loads an sap.ui.core.UIComponent which is the self-contained encapsulation of the whole
application.
The sap.ui.core.UIComponent (henceforth referred to as the ' Component ') is defined in the Component.js file in the same folder as index.html; it
is found through the specification of resource locations in the bootstrap tag - see the Bootstrap section for more details. The Component has metadata
defined, which includes application-level configuration and routing information. Routing is the key best practice navigation mechanism and for non-trivial apps
supersedes sap.ui.core.EventBus-based navigation, and even shared-controller-based access to the top-level navigation control (such as an
sap.m.NavContainer).
In the initialization part of the Component, models are created and set on the component: the main model that connects to the OData source, and some helper
models for internationalization (i18n), where the texts are loaded from a local resource messageBundle.properties, and device detection based
arrangement of user interface (UI) controls. Further, the initialization of the sap.ui.core.routing.Router (the Router) is performed. In the main section
of the Component the root view (' App ') is instantiated. Like all the other views in this sample app, the root view is an XML view.

The Views and Fragments


The root view App , defined in App.view.xml, is very simple and contains a single sap.m.SplitApp control. Neither the master nor the detail pages
aggregations are filled at this time in the view definition; this is all managed by the Router, which is also the reason why there's no explicit requirement for a
controller connected to this view.
The visible part of the sample app is provided by three main XML views and a fragment. When displayed on devices other than a smartphone, there are two
views shown - the master and the detail. Initially this is the Master.view.xml and the Detail.view.xml . Each of these views contain an sap.m.Page.
There's also NotFound.view.xml, to be used when no view can be matched to the resource requested. There is also the AddProduct view
(AddProduct.view.xml) which is shown in the detail pages aggregation when the add product button is pressed. There are no direct dependencies
between views.
In the Detail view there is a couple of sap.ui.layout.form.SimpleForm controls to display the supplier address and category details. These controls are
defined separately in XML fragments SupplierAddressForm.fragment.xml and CategoryInfoForm.fragment.xml. They are included in the Detail
view using the sap.ui.core.Fragment element. Finally, there's another fragment NameRequiredDialog.fragment.xml, where a Dialog control is
defined, to be shown if no name is specified in the new product form.

Other Files
PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 4 of 36

The Formatter.js file contains formatting utilities that are used in various controls within the Master and Detail views.
The css folder contains style.css which has a very small amount of custom CSS, specifically to position the "not found" message further down that it
would normally sit.

The Overall Folder Structure


The folder structure containing all the resources mentioned in this overview looks like this:
tdg/
|
+-|
|
|
+-|
|
|
+-|
|
|
+-|
|
|
|
|
|
|
|
|
|
|
|
|
+-+-+--

css/
|
+-- style.css
i18n/
|
+-- messageBundle.properties
util/
|
+-- Formatter.js
view/
|
+-+-+-+-+-+-+-+-+-+-+--

AddProduct.controller.js
AddProduct.view.xml
App.view.xml
CategoryInfoForm.fragment.xml
Detail.controller.js
Detail.view.xml
Master.controller.js
Master.view.xml
NameRequiredDialog.fragment.xml
NotFound.view.xml
SupplierAddressForm.fragment.xml

Component.js
index.html
MyRouter.js

So now we have an idea of what our app should consist of, let's start building it.
Step 1: Index
Because an application would normally live in a folder, and the default file that web servers return when the folder itself is requested is often configured to be
index.html, the name of our app's index HTML file is indeed index.html.
Step 2: Component
Step 3: Navigation and Routing
A new Routing mechanism was introduced to SAPUI5 in release 1.16. For in-app navigation, this supersedes previous techniques such as using the
sap.ui.core.EventBus or sharing navigation-container specific controller code amongst aggregated pages.
Step 4: Internationalization
For localization, or internationalization (i18n), use data binding and the sap.ui.model.resource.ResourceModel as a named model. This allows
easily using translation keys instead of the actual texts. The alternative, using ResourceBundles , requires code execution for every translated text set on
a control.
Step 5: Device Model
Before we get to the App view, there are a couple of other areas to cover. The first is the Device Model that we saw briefly in the Component section.
Step 6: Custom Utilities
When developing an application there is always the need to reuse JavaScript coding in multiple views and controllers. This is achieved by storing this code
in separate files in the util folder. The SAPUI5 modularization concept is used to dynamically load this code at runtime.
Step 7: Model View Controller
We're using the Model View Controller (MVC) concept, and this starts with a root View that the Component instantiates - the App view.
Step 8: Master View
Flushed with the success of seeing our SplitApp container on the screen, we'll add the first main view, Master, replacing the empty skeleton we used in the
Model View Controller section.
Step 9: Detail View
We'll now add the Detail view, replacing the skeleton placeholder we had up until now.
Step 10: Detail XML Fragments
In the Detail View section, we referenced XML fragments in the sap.m.IconTabFilters. These fragments are separately maintainable and reusable
collections of control declarations.
Step 11: AddProduct View
Let's round out the core functionality of the app by bringing in the facility to add a new product.
Step 12: NotFound View

1.1 Step 1: Index


Because an application would normally live in a folder, and the default file that web servers return when the folder itself is requested is often configured to be
index.html, the name of our app's index HTML file is indeed index.html.

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 5 of 36

Minimal Content
The heart of an application is defined in its Component part, which means that we should aim for very minimal content in the index.html file itself. Like this:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta charset="UTF-8">
<title>TDG Demo App</title>
<script id="sap-ui-bootstrap"
src="/path/to/resources/sap-ui-core.js"
data-sap-ui-libs="sap.m"
data-sap-ui-theme="sap_bluecrystal"
data-sap-ui-xx-bindingSyntax="complex"
data-sap-ui-resourceroots='{
"sap.ui.demo.tdg": "./"
}'>
</script>
<script>
sap.ui.getCore().attachInit(function() {
new sap.m.Shell({
app: new sap.ui.core.ComponentContainer({
height : "100%",
name : "sap.ui.demo.tdg"
})
}).placeAt("content");
});
</script>
</head>
<body class="sapUiBody" id="content" />
</html>
We have a header and the standard variant SAPUI5 bootstrap, the application startup, and a particular focus on resources and namespaces.

Header and Bootstrap


In the header, we have the HTML5 document type declaration, and a couple of meta tags. One that instructs Internet Explorer to use the newest rendering
mode; for SAPUI5 mobile this would be in preparation for future IE support, for SAPUI5 desktop this is required to make sure IE does not use a compatibility
mode. The other is to declare that the character set used in the app is UTF8 (change this if required to whatever is necessary).
We also have the SAPUI5 bootstrap; in this example we have the so-called "standard variant", pulling in the SAPUI5 core from wherever it is normally served.
With the bootstrap there are some data parameters. These parameters serve to load the sap.m library, so that we can specify the sap.m.Shell control later
on (data-sap-ui-libs), declare the theme to be "Blue Crystal" (data-sap-ui-theme), set up the extended syntax for calculated fields in property
bindings (data-sap-ui-xx-bindingSyntax) and, finally, where the resources, or artifacts, can be found (data-sap-ui-resourceroots - see below
for more information on this).

The Application Startup


Once the SAPUI5 core has been loaded in the bootstrap, we start the application on the Init event via sap.ui.getCore().attachInit. Starting our app
involves instantiating an sap.ui.core.ComponentContainer and placing it inside an sap.m.Shell in the body of our HTML document. Note that we're
using the "content" id directly on the HTML <body> element, rather than in a child <div> element.
The use of the Shell is to control the width of the app on the desktop. On particularly large monitors, a full-width rendering of an application would be too wide.
Placing the app's core inside a Shell container will give you appropriate left and right vertical margins.

Resources and Namespaces


Your application should live inside a namespace. This sample application's identifier is "tdg", within the "sap.ui.demo" namespace, thus the name
sap.ui.demo.tdg is the fully qualified identifier for this application.
This name is used in two places in the index file:
in the bootstrap's data-sap-ui-resourceroots data parameter
in the specification of the Component to load into the sap.ui.core.ComponentContainer
Pairing a namespace and folder location in the data-sap-ui-resourceroots declares where artifacts with that namespace can be found. In this example
we are saying that the artifacts starting "sap.ui.demo.tdg" are to be found in the same folder ("./") as this index file.

Note
While we're here, notice the way that the namespace and folder location are specified in a JSON structure inside the value of an HTML tag's attribute.
You'll come across this syntax again when we see the use of complex binding syntax in declarative (XML) views, where paths, types and formatters are
specified for property bindings.

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 6 of 36

When the component is specified with the name property of the sap.ui.core.ComponentContainer, it is by means of the "sap.ui.demo.tdg" namespace.
By default, the container will look for a component called "Component" in this namespace, which thus translates to sap.ui.demo.tdg.Component. This
will be found in the file Component.js in the same folder, because we've specified this same folder as being where resources in the sap.ui.demo.tdg
namespace are to be found.

Further Notes
Specific applications may require additional script tags to load external files or some HTML structure within the body or a script tag before loading SAPUI5 to
configure the SAPUI5 runtime. This suggestion here does not say such extensions are not allowed.

Progress Check
This is the first step, so we're not expecting much. All we have is the index.html file.
tdg/
|
+-- index.html
But it's the root container for our whole application, so at least we can try to call it up in our browser. This is what we get.

Ok. Not a lot. But not an entirely blank screen - we can see already that the styling is from the Blue Crystal theme. Most importantly, we see the significant
error in the developer console:
Uncaught Error: failed to load 'sap/ui/demo/tdg/Component.js' from ./Component.js: 404 Not Found
This is not unexpected. The Component Container has been instantiated and is trying to retrieve the Component ... but failing. In the next step, we'll look at
making that Component available.

1.2 Step 2: Component


The component encapsulates all of our sample application. That doesn't mean that all the view definitions and application logic is to be found here, rather, it
represents an independent unit which can be instantiated either inside a container or elsewhere. From this unit, the child artifacts, such as views, are brought
into play.
Components do not need to be complete applications; they can also be smaller clusters of functionality. Here we are using an sap.ui.core.UIComponent
to define our "application unit".
We can view our sample application's Component as having three main sections, and we'll cover them one by one. For that we create a new JavaScript file in
the same location as our index.html and call it Component.js.

Metadata
After the initial declaration of the component itself, with the fully qualified name sap.ui.demo.tdg.Component, and the loading of our custom router
module, we start with a definition of the component as an sap.ui.core.UIComponent.
jQuery.sap.declare("sap.ui.demo.tdg.Component");
jQuery.sap.require("sap.ui.demo.tdg.MyRouter");
sap.ui.core.UIComponent.extend("sap.ui.demo.tdg.Component", {
As is typical with components, the first thing we see is the component's settings configuration, in the form of the metadata. We'll tackle the configuration in
three arbitrary sections: general, config and routing.

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 7 of 36

General
metadata : {
name : "TDG Demo App",
version : "1.0",
includes : [],
dependencies : {
libs : ["sap.m", "sap.ui.layout"],
components : []
},
rootView : "sap.ui.demo.tdg.view.App",
In this general section we specify the name of the app and its version. We also specify the library dependencies, which, when the component is instantiated,
will be loaded directly. In this case it is the sap.m and sap.ui.layout libraries that we need (the latter being for the
sap.ui.layout.form.SimpleForm controls that we use to display supplier and category information in the Detail view. This application component
doesn't rely on any other components or other files, so the rest of this section is empty.
Ultimately the component, being visual (sap.ui.core.component.UIComponent) needs to return something for the user to see. In this case it's the App
view (in App.view.xml in the view folder). You can do this by specifying a createContent function in the component and returning the view there. This is
a similar pattern to JavaScript-based view and fragment definitions. However, if you don't need to enhance your view (with additional data, for example) and
you would just be returning a fresh instantiation, then you should avoid using the createContent function, and instead, specify the view as shown here,
using the rootView metadata parameter. The component itself will then take care of the instantiation.
Config
config : {
resourceBundle : "i18n/messageBundle.properties",
serviceConfig : {
name : "Northwind",
serviceUrl : "http://services.odata.org/V2/(S(sapuidemotdg))/OData/OData.svc/"
}
},

Note
For the above config settings to work, you need to disable the web security of your browser. To do this, start your Chrome browser with the arguments -disable-web-security. This is vital, as the service you are trying to consume here lies on a different server. Another way of overcoming this problem
is to configure a proxy URL on your server that redirects requests to the Northwind service. You can use a grunt task to configure a proxy such as this.
The sample application is fairly simple and does not require much configuration. Here we have the name of the resource bundle we'll be using for our
internationalization, and the URL of the OData service we'll be using for our main, or "domain" model.
Routing
routing: {
//...
}
},
The routing configuration is not shown here; please refer to the separate section on Navigation and Routing for detailed information.

Initialization
Now that we have the configuration out of the way, it's time to initialize the component.
init : function() {
sap.ui.core.UIComponent.prototype.init.apply(this, arguments);
var mConfig = this.getMetadata().getConfig();
// always use absolute paths relative to our own component
// (relative paths will fail if running in the Fiori Launchpad)
var rootPath = jQuery.sap.getModulePath("sap.ui.demo.tdg");
// set i18n model
var i18nModel = new sap.ui.model.resource.ResourceModel({
bundleUrl : [rootPath, mConfig.resourceBundle].join("/")
});
this.setModel(i18nModel, "i18n");
// Create and set domain model to the component
var sServiceUrl = mConfig.serviceConfig.serviceUrl;
var oModel = new sap.ui.model.odata.ODataModel(sServiceUrl, true);
this.setModel(oModel);
// set device model
var deviceModel = new sap.ui.model.json.JSONModel({
isTouch : sap.ui.Device.support.touch,
isNoTouch : !sap.ui.Device.support.touch,
isPhone : sap.ui.Device.system.phone,
isNoPhone : !sap.ui.Device.system.phone,
listMode : sap.ui.Device.system.phone ? "None" : "SingleSelectMaster",
listItemType : sap.ui.Device.system.phone ? "Active" : "Inactive"
});

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 8 of 36

deviceModel.setDefaultBindingMode("OneWay");
this.setModel(deviceModel, "device");
this.getRouter().initialize();

},
});
In the component initialization, we perform various setup activities. First, we retrieve the component configuration from the metadata section shown earlier.
We're particularly interested in the service URL as that is going to be used when we create our domain model.
There are actually three models that are created here, and they're all set on the component, meaning they're available to the views within. After setting the
models on the component, we initialize the router.
Internationalization Model
This is for application texts that can be translated. See the section on Internationalization for more details.
Domain Model
The domain model is also created based upon the service URL specified in the configuration. This is usually an OData service.
Device Model
Building a responsive app means ensuring that the controls behave appropriately on different devices and screen sizes. Some responsiveness within controls
is implicit, where as other aspects must be determined explicitly, based on the device on which it is running. For these latter cases we need to detect the
device and use the information to determine how to configure the control's behavior.
Detecting the device is done using the sap.ui.Device support, and the information obtained is stored in a model. See the Device Model section for more
details.

Progress Check
We've added Component.js, so our app folder content looks like this:
tdg/
|
+-- Component.js
+-- index.html
But we're still getting a Blue Crystal styled empty screen:

This time, we see an error in the console:


Uncaught Error: failed to load 'sap/ui/demo/tdg/Component.js' from ./Component.js: Error:
failed to load 'sap/ui/demo/tdg/MyRouter.js' from ./MyRouter.js: 404 - Not Found
This is also not unexpected. We've said we need the sap.ui.demo.tdg.MyRouter via the call to jQuery.sap.require, but we don't have it yet.

Related Information
Components
Navigation

1.3 Step 3: Navigation and Routing


A new Routing mechanism was introduced to SAPUI5 in release 1.16. For in-app navigation, this supersedes previous techniques such as using the
sap.ui.core.EventBus or sharing navigation-container specific controller code amongst aggregated pages.

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 9 of 36

While these previous techniques work well for intra-application navigation, they don't cater for the requirements for bookmarking and resuming application User
Interface (UI) state.

Navigation
Applications exist independently, and navigation within those applications usually starts at the root control, often a container such as an sap.m.App (or
sap.m.NavContainer) or an sap.m.SplitApp (or sap.m.SplitContainer). If you want to only be able to jump into your application at the starting
point, then sharing navigation-container code is a technique that will work. However, it will not give you the ability to bookmark a certain position within the
application, and it will not support resuming application flow from that bookmarked position.
Consider our app. Here we see the UI state showing the details for a particular product (Pink Lemonade), and specifically the supplier's address details.

Without routing, navigation to this UI state would require the user to find the product in the master list, select it, and then ensure that the supplier's address
was selected in the detail view. Routing gives the application programmer the ability to support navigation directly to this UI state.
With routing, and appropriate application logic, the UI state in the screenshot could be directly navigated to from this URL:
http://<host>:<port>/path/to/app/index.html#/Products(6)/supplier

Routing
The navigation described above is achieved through use of the Routing mechanisms in SAPUI5. These are split between core () and sap.m (class).

Setup
In our app, we define and set up routing in the component. Let's examine the relevant sections of Component.js now.
Loading of Custom Router
We use a custom Router sap.ui.demo.tdg.MyRouter so in our component we make sure the module is loaded and available:
jQuery.sap.require("sap.ui.demo.tdg.MyRouter");
Routing Configuration
In our component's metadata, we define the routing configuration. This configuration is used for initializing the router.
routing : {
config : {
routerClass : sap.ui.demo.tdg.MyRouter,
viewType : "XML",
viewPath : "sap.ui.demo.tdg.view",
targetAggregation : "detailPages",
clearTarget : false
},
routes : [
{
pattern : "",
name : "main",
view : "Master",
targetAggregation : "masterPages",
targetControl : "idAppControl",
subroutes : [
{

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 10 of 36

pattern : "{product}/:tab:",
name : "product",
view : "Detail"
}
]
},
{
name : "catchallMaster",
view : "Master",
targetAggregation : "masterPages",
targetControl : "idAppControl",
subroutes : [
{
pattern : ":all*:",
name : "catchallDetail",
view : "NotFound"
}
]
}
]
}
With the config section, we define some default values:
we have a custom router class (sap.ui.demo.tdg.MyRouter)
the views are XML
the absolute path to our view definitions is sap.ui.demo.tdg.view
unless stated otherwise, when the router instantiates a view, it should place it in the detail part of our sap.m.SplitApp control (via the detailPages
aggregation)
we don't want the target aggregation (detailPages) to be cleared before views are added, so we specify false for the clearTarget parameter
The routes section is an array of routing configuration objects representing routes that we want to handle. Each configuration object has a single mandatory
parameter name. All other parameters are optional.
We have a "main" route that causes the Master view to be placed in the masterPages aggregation of the sap.m.SplitApp, which is available via its id
'idAppControl'. There is also a subroute defined, in particular:
the Detail view (route name 'product') which causes the Detail view to be instantiated and placed into the detailPages aggregation of the
sap.m.SplitApp. There are two segments that we're expecting in the URL pattern for this route:
the product context, via the section {product} - in our example above, this would be "Products(6)"
the optional information tab, via the section :tab: , which will determine which sap.m.IconTabFilter will be pre-selected - in our example
above, this would be the "supplier" tab
We also have a 'catchall' route and subroute pair which is defined so that a sensible message (in this case the details in the NotFound view) can be shown to
the user if they try to navigate, via a URL, to a resource that is not valid as far as the app is concerned.
Please refer to the navigation and routing documentation for a full explanation.
Router Initialization
The router is initialized by the component in the init function:
init : function() {
...
this.getRouter().initialize();

The initialize method will start the routing it will parse the initial hash, create the needed views, start listening to hash changes and trigger the router events.
The router is retrieved by a call of getRouter on the component.
Custom Routing
Our custom routing is performed in a module sap.ui.demo.tdg.MyRouter, which is an extended standard router. It's defined in a MyRouter.js file in
the application's root folder.
This is what MyRouter.js contains:
jQuery.sap.require("sap.m.routing.RouteMatchedHandler");
jQuery.sap.require("sap.ui.core.routing.Router");
jQuery.sap.declare("sap.ui.demo.tdg.MyRouter");
sap.ui.core.routing.Router.extend("sap.ui.demo.tdg.MyRouter", {
constructor : function() {
sap.ui.core.routing.Router.apply(this, arguments);
this._oRouteMatchedHandler = new sap.m.routing.RouteMatchedHandler(this);
},
myNavBack : function(sRoute, mData) {
var oHistory = sap.ui.core.routing.History.getInstance();
var sPreviousHash = oHistory.getPreviousHash();
// The history contains a previous entry
if (sPreviousHash !== undefined) {
window.history.go(-1);
} else {
var bReplace = true; // otherwise we go backwards with a forward history
this.navTo(sRoute, mData, bReplace);

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 11 of 36

}
},
/**
* @public Changes the view without changing the hash
*
* @param oOptions {object} must have the following properties
* <ul>
* <li> currentView : the view you start the navigation from.</li>
* <li> targetViewName : the fully qualified name of the view you want to navigate to.</li>
* <li> targetViewType : the viewtype eg: XML</li>
* <li> isMaster : default is false, true if the view should be put in the master</li>
* <li> transition : default is "show", the navigation transition</li>
* <li> data : the data passed to the navContainers livecycle events</li>
* </ul>
*/
myNavToWithoutHash : function (oOptions) {
var oSplitApp = this._findSplitApp(oOptions.currentView);
// Load view, add it to the page aggregation, and navigate to it
var oView = this.getView(oOptions.targetViewName, oOptions.targetViewType);
oSplitApp.addPage(oView, oOptions.isMaster);
oSplitApp.to(oView.getId(), oOptions.transition || "show", oOptions.data);
},
backWithoutHash : function (oCurrentView, bIsMaster) {
var sBackMethod = bIsMaster ? "backMaster" : "backDetail";
this._findSplitApp(oCurrentView)[sBackMethod]();
},
destroy : function() {
sap.ui.core.routing.Router.prototype.destroy.apply(this, arguments);
this._oRouteMatchedHandler.destroy();
},
_findSplitApp : function(oControl) {
sAncestorControlName = "idAppControl";
if (oControl instanceof sap.ui.core.mvc.View && oControl.byId(sAncestorControlName)) {
return oControl.byId(sAncestorControlName);
}
return oControl.getParent() ? this._findSplitApp(oControl.getParent(), sAncestorControlName) : null;
}
});

Use
Once the routing has been configured and initialized, it can be used, in the controllers, to marshal the appropriate data and UI components, according to the
URL pattern that is matched. This is done by attaching a function to the router's routeMatched event. Here are two examples from the Master and Detail
views.
Easy access of the router and the components eventbus. Getting the eventbus and the router is needed in most of the controllers. So adding a custom
controller for reuse purposes makes sense. Here is how it looks like:
jQuery.sap.declare("sap.ui.demo.tdg.util.Controller");
sap.ui.core.mvc.Controller.extend("sap.ui.demo.tdg.util.Controller", {
getEventBus : function () {
var sComponentId = sap.ui.core.Component.getOwnerIdFor(this.getView());
return sap.ui.component(sComponentId).getEventBus();
},
getRouter : function () {
return sap.ui.core.UIComponent.getRouterFor(this);
}
});
Earlier we saw how the router was retrieved by calling getRouter on the component. We can also access the router with a static call to
sap.ui.core.UIComponent.getRouterFor.
Route Match in Master
Attaching to events of the router should normally be set up in the controller's initialization event onInit.
onInit: function() {
//on phones, we will not have to select anything in the list so we don't need to attach to events
if (sap.ui.Device.system.phone) {
return;

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 12 of 36

}
this.getRouter().attachRoutePatternMatched(this.onRouteMatched, this);

},
onRouteMatched : function(oEvent) {
var sName = oEvent.getParameter("name");
if (sName !== "main") {
return;
}
//Load the detail view in desktop
this.getRouter().myNavToWithoutHash({
currentView : this.getView(),
targetViewName : "sap.ui.demo.tdg.view.Detail",
targetViewType : "XML"
});
//Wait for the list to be loaded once
this.waitForInitialListLoading(function () {
//On the empty hash select the first item
this.selectFirstItem();
});
},
...
},
In the handler we check to see what the name of the matched route is and act appropriately. In this case, we are only looking for the main route.
Route Match in Detail controller
Similar to the routing use in the Master controller, we also want to react in the Detail:
onInit : function() {
this.oInitialLoadFinishedDeferred = jQuery.Deferred();
if(sap.ui.Device.system.phone) {
//don't wait for the master on a phone
this.oInitialLoadFinishedDeferred.resolve();
} else {
this.getView().setBusy(true);
this.getEventBus().subscribe("Master", "InitialLoadFinished", this.onMasterLoaded, this);
}
this.getRouter().attachRoutePatternMatched(this.onRouteMatched, this);
}, onRouteMatched : function(oEvent) {
var oParameters = oEvent.getParameters();
jQuery.when(this.oInitialLoadFinishedDeferred).then(jQuery.proxy(function () {
var oView = this.getView();
// when detail navigation occurs, update the binding context
if (oParameters.name !== "product") {
return;
}
var sProductPath = "/" + oParameters.arguments.product;
this.bindView(sProductPath);
var oIconTabBar = oView.byId("idIconTabBar");
oIconTabBar.getItems().forEach(function(oItem) {
oItem.bindElement(sap.ui.demo.tdg.util.Formatter.uppercaseFirstChar(oItem.getKey()));
});
// Which tab?
var sTabKey = oParameters.arguments.tab || "supplier";
this.getEventBus().publish("Detail", "TabChanged", { sTabKey : sTabKey });
if (oIconTabBar.getSelectedKey() !== sTabKey) {
oIconTabBar.setSelectedKey(sTabKey);
}
}, this));
},
bindView : function (sProductPath) {

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 13 of 36

var oView = this.getView();


oView.bindElement(sProductPath);
//Check if the data is already on the client
if(!oView.getModel().getData(sProductPath)) {
// Check that the product specified actually was found.
oView.getElementBinding().attachEventOnce("dataReceived", jQuery.proxy(function() {
var oData = oView.getModel().getData(sProductPath);
if (!oData) {
this.showEmptyView();
this.fireDetailNotFound();
} else {
this.fireDetailChanged(sProductPath);
}
}, this));
} else {
this.fireDetailChanged(sProductPath);
}
},
},
Here, on a product route match, we set the Detail view's binding context to the specific product context that was selected in the URL ("Products(6)").
We should also deal with the situation where the Product with the ID specified does not exist, by telling the user the Product wasn't found. How do we do this?
Rather than just present empty bindings, we check whether the model has already loaded the data (maybe the user already has viewed this detail). If we dont
find data locally, a request will be send by the binding. We can set a handler to be fired on a dataReceived event relating to the element binding. On that
event, we can then check the actual data in the model to make sure it's been possible to retrieve it. If not, we can navigate the user to a 'not found' display.
We also tell the master that the notFound view was shown and the tab was selected. So we can clear the selection if there was no entry found. Also we want
to write the correct tab in the url. Since the master is doing this, we have to inform it.
We also ensure that the binding of the sap.m.IconTabFilters are set correctly. Finally, we make sure that the pre-selected tab in the
sap.m.IconTabBar is the one that was specified in the URL (the optional last part of the pattern, denoted by :tab:), defaulting to the supplier tab if none
was specified.

Progress Check
We've added MyRouter.js, so our app folder content looks like this:
tdg/
|
+-- Component.js
+-- index.html
+-- MyRouter.js
But we're still getting the Blue Crystal style empty screen:

This time, however, we see a slightly different error in the console:


Uncaught Error: resource sap/ui/demo/tdg/view/App.view.xml could not be loaded from
./view/App.view.xml. Check for 'file not found' or parse errors.

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 14 of 36

Of course, we've specified that the rootView for this component should be sap.ui.demo.tdg.view.App, and we've said that all resources in the
sap.ui.demo.tdg namespace are in this same folder, so we can see that as we don't have a view subfolder, or anything in it, there's going to be a problem.
Note however, that you also may encounter this message when you do have the view XML that your app expects; if you do, check that the XML is well formed
and expresses the controls and their properties and aggregations correctly - otherwise, you may get this error because it was not possible to parse.

Related Information
Navigation

1.4 Step 4: Internationalization


For localization, or internationalization (i18n), use data binding and the sap.ui.model.resource.ResourceModel as a named model. This allows easily
using translation keys instead of the actual texts. The alternative, using ResourceBundles , requires code execution for every translated text set on a control.
Let's examine the relevant code that we briefly saw earlier in the Component section. The i18n model is set up in the Component's initialization section, based
upon configuration in the component's metadata:
// always use absolute paths relative to our own component
// (relative paths will fail if running in the Fiori Launchpad)
var rootPath = jQuery.sap.getModulePath("sap.ui.demo.tdg");
// set i18n model
var i18nModel = new sap.ui.model.resource.ResourceModel({
bundleUrl : [rootPath, mConfig.resourceBundle].join("/")
});
this.setModel(i18nModel, "i18n");
Use "i18n" as the name for the model; use only one global property file that contains all texts used in the application.
The configuration specifies the relative location of the resource bundle. This is made absolute (in relation to our app's namespace) by use of the
jQuery.sap.getModulePath utility function, and then used to create the named model, before it is set on the component. Here's an excerpt from the
messageBundle.properties file in the i18n folder:
masterTitle=Products
masterFooterAddButtonTooltip=Add new product
masterListNoDataText=No products
notFoundTitle=Not Found
notFoundText=The requested resource was not found
addProductTitle=Add Product
addProductButtonSave=Save
...
Group view specific texts with prefixes, as shown here (master, notFound, addProduct). If you have texts that are common across all views of your app, do
not apply a prefix, so as to avoid redundancies. Note that here, the name of the resource bundle file has no locale-specific suffix. This is the 'catchall' case.
SAPUI5 will attempt to load the most specific locale-specific resource bundle file, with graceful degradation down to this catchall case. For example, with a
UK-based browser, and no explicit language-related URL parameters, the SAPUI5 runtime, given the above configuration, will attempt to load the following files
in this order:
1. messageBundle_en_GB.properties
2. messageBundle_en.properties
3. messageBundle.properties
Texts for translation keys in more specific resource bundles (loaded earlier) will take precedence over texts for the same translation keys in less specific
resource bundles (loaded later). In our sample application we only have the least specific messageBundle.properties file.

Note
Note that when using the sap.ui.model.resource.ResourceModel, the translation keys are referred to without the slash prefix that normally
denotes a root data property. In other words, in the data binding, use {i18n>masterTitle} rather than {i18n>/masterTitle}.

Progress Check
We've added the i18n subfolder, and the messageBundle.properties file in it, so our app folder content looks like this:
tdg/
|
+-|
|
|
+-+-+--

i18n/
|
+-- messageBundle.properties
Component.js
index.html
MyRouter.html

But as you might expect, we'll get the same outcome as before, as we still don't have the App view. Let's press on.

1.5 Step 5: Device Model


Before we get to the App view, there are a couple of other areas to cover. The first is the Device Model that we saw briefly in the Component section.
As a reminder, this is what it looks like:
// set device model
var deviceModel = new sap.ui.model.json.JSONModel({
isTouch : sap.ui.Device.support.touch,

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 15 of 36

isNoTouch : !sap.ui.Device.support.touch,
isPhone : sap.ui.Device.system.phone,
isNoPhone : !sap.ui.Device.system.phone,
listMode : sap.ui.Device.system.phone ? "None" : "SingleSelectMaster",
listItemType : sap.ui.Device.system.phone ? "Active" : "Inactive"
});
deviceModel.setDefaultBindingMode("OneWay");
this.setModel(deviceModel, "device");
Using the sap.ui.Device library, various runtime platform characteristics are determined, and stored in a client side JSON model. The properties in this
model follow a standard pattern; it's advisable to use the same properties, even if you aren't going to utilise all of them. Here's a table showing which properties
are used where in our demo app.
Property

Description

Usage

isTouch

The device has touch support

nowhere in this particular app

isNoTouch

Opposite of isTouch, for opposite declarative boolean Master.view.xml, to show a refresh button on the

isPhone

usage

SearchField if the device doesn't have touch support

The device is a smartphone

Detail.view.xml, to control whether the Page's


navButton is displayed or not (only when the device is
a smartphone)

isNoPhone

Opposite of isPhone, for opposite declarative boolean nowhere in this particular app
usage

listMode

Setting the selection mode of a List control

Master.view.xml, to have the List in


SingleSelectMaster mode unless the device is a
phone

listItemType

Setting the type of each item in a List control's item

Master.view.xml, to have the type of each List item

aggregation

(ObjectListItem controls) set to Active if the device


is a phone (otherwise Inactive)

The List's mode property and the ObjectListItem's type property are similar and control how items are selectable. On a smartphone the selection should
be via the item (listMode " None ", listItemType " Active "), otherwise it should be via the List itself (listMode " SingleSelectMaster ",
listItemType " Inactive ").The Device Model is a JSON client side model, and should be set with a One Way binding mode. This is to prevent unintentional
modification of the detected device type and support when control properties are changed through UI use. Also, the recommendation is always to use "device"
for the name of this named model.

Progress Check
There is no further progress beyond the previous section; this Device Model is part of the Component, which we already have. While the information in the
Device Model influences visual aspects of our app, these will be apparent in other sections.

1.6 Step 6: Custom Utilities


When developing an application there is always the need to reuse JavaScript coding in multiple views and controllers. This is achieved by storing this code in
separate files in the util folder. The SAPUI5 modularization concept is used to dynamically load this code at runtime.
One prominent example is the reuse of formatters in data bindings. In our sample application, we have properties in data bindings that need to be formatted.
The code is defined in a Formatter.js file in the util folder.
Here's the content of the file:
jQuery.sap.declare("sap.ui.demo.tdg.util.Formatter");
sap.ui.demo.tdg.util.Formatter = {
uppercaseFirstChar : function(sStr) {
return sStr.charAt(0).toUpperCase() + sStr.slice(1);
},
discontinuedStatusState : function(sDate) {
return sDate ? "Error" : "None";
},
discontinuedStatusValue : function(sDate) {
return sDate ? "Discontinued" : "";
},
currencyValue : function (value) {
return parseFloat(value).toFixed(2);
}
};
Here are a couple of examples of where these formatting functions are used - one from the Master view:
<ObjectStatus
text="{
path: 'Rating',
formatter: 'sap.ui.demo.tdg.util.Formatter.ratingStatusValue'
}"

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 16 of 36

state="{
path: 'Rating',
formatter: 'sap.ui.demo.tdg.util.Formatter.ratingStatusState'
}" />
and one from the Detail view:
<ObjectHeader
title="{Name}"
icon="sap-icon://database"
number="{
path: 'Price',
formatter: 'sap.ui.demo.tdg.util.Formatter.currencyValue'
}"
numberUnit="USD">
...
</ObjectHeader>
We see from the Master view example that the formatter functions are used not only to format numeric values, but also to supply the appropriate value from an
enumeration (such as the sap.ui.core.ValueState for an sap.m.ObjectStatus).

Progress Check
After adding the Formatter.js file in the new util subfolder, our app folder content looks like this:
tdg/
|
+-|
|
|
+-|
|
|
+-+-+--

i18n/
|
+-- messageBundle.properties
util/
|
+-- Formatter.js
Component.js
index.html
MyRouter.html

Related Information
Modularization and Resource Handling

1.7 Step 7: Model View Controller


We're using the Model View Controller (MVC) concept, and this starts with a root View that the Component instantiates - the App view.
Right now, the error in the console is suggesting that an App view, specifically the file App.view.xml, is missing. Let's take this opportunity to step back
and look at what we're going to build.
The app view is rather simple, in that it contains just a single control - an sap.m.SplitApp - and does not have a corresponding controller. There is no
requirement for a controller as the routing and navigation logic is provided by the Router that we've already defined.
Further, there's no requirement for any great detail in the SplitApp, as the aggregation content is also controlled by the Router. This is what the App view
looks like, in its entirety:
<mvc:View
xmlns:mvc="sap.ui.core.mvc"
displayBlock="true"
xmlns="sap.m">
<SplitApp id="idAppControl" />
</mvc:View>
A SplitApp is a so-called 'container' control, and inheriting from sap.m.NavContainer, has two container sections, in the form of aggregations:
masterPages and detailPages. These aggregations, as we already saw in the Routing configuration in the Component, are managed by the Router, and
we don't need to specify anything directly here. While we're already getting some separation of concerns, it's more from the fact that we're using a container
control. We're going to go one level deeper and use the MVC approach within the contained controls. The 'pages' suffix of these aggregation names suggest
that the sap.m.Page is a candidate for aggregation, however, we're aggregating views, as we can see in the Routing configuration. This is a common pattern.
Aggregating views within the SplitApp container control allows us to develop the different parts of the app independently, handling the UI and logic in discrete
sections. We've already seen the component overview schematic in Part 1, so let's look at a tree-based summary of the controls and MVC elements now (all
filenames are relative to the app's root folder):
control:UIComponent (Component.js)
|
+-- view:App (view/App.view.xml)
|
+-- control:SplitApp
|
+-- aggregation:masterPages
|
|
|
+-- view:Master (view/Master.view.xml)
|
|
|
+-- controller: Master (view/Master.controller.js)
|

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 17 of 36

+-- aggregation:detailPages
|
+-- view:Detail (view/Detail.view.xml)
|
|
|
+-- controller:Detail (view/Detail.controller.js)
|
+-- view:NotFound (view/NotFound.view.xml)
|
+-- view:AddProduct (view/AddProduct.view.xml)
|
+-- controller:AddProduct (view/AddProduct.controller.js)

Progress Check
We'll add the App view, and also a couple of temporary skeleton XML declarations for the Master and Detail views (this is mostly so we can see a little bit of
progress). Our skeleton XML declarations for the Master and Detail views look like this:
<mvc:View xmlns:mvc="sap.ui.core.mvc" />
In other words, completely empty views. Now our app folder content now looks like this:
tdg/
|
+-|
|
|
+-|
|
|
|
|
+-+-+--

i18n/
|
+-- messageBundle.properties
view/
|
+-- App.view.xml
+-- Detail.view.xml
+-- Master.view.xml
Component.js
index.html
MyRouter.html

At this stage things look a little more interesting than the previous empty screen. We can see the core structure of the SplitApp.

There are no relevant error messages in the console - the runtime is delivering exactly what we asked for (a couple of empty views in the SplitApp container
control).
In fact, because there's now actually something visible, let's have a look at an alternative screenshot, where the browser window is maximised to full screen:

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 18 of 36

Here we can see that the Master and Detail aggregation proportions remain, despite the extreme width of the browser window; there are margins on the left and
right sides. This is due to the sap.m.Shell control that we introduced in the index.html.

1.8 Step 8: Master View


Flushed with the success of seeing our SplitApp container on the screen, we'll add the first main view, Master, replacing the empty skeleton we used in the
Model View Controller section.

View
Let's examine the view definition bit by bit.
<mvc:View
controllerName="sap.ui.demo.tdg.view.Master"
displayBlock="true"
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m">
We start with the view declaration itself, which also points to a corresponding controller, which we'll introduce shortly. Note that in all the views in this app,
we're specifying sap.m as the default namespace (this means that elements that don't have an XML namespace prefix belong to the sap.m library). The
displayBlock="true" declaration is to prevent scrollbars appearing.
<Page
id="page"
title="{i18n>masterTitle}">
<subHeader>
<Bar id="searchBar">
<contentMiddle>
<SearchField
id="searchField"
showRefreshButton="{device>/isNoTouch}"
search="onSearch"
tooltip="{i18n>masterSearchTooltip}"
width="100%">
</SearchField>
</contentMiddle>
</Bar>
</subHeader>
The Master view contains a single top-level control - an sap.m.Page. As a UI unit this makes sense especially being within the context of the SplitApp's
masterPages aggregation. The page's title is determined via our Internationalization mechanism, and has a subHeader aggregation, which is a single
aggregation that expects an sap.m.Bar. In the middle of the Bar we have an sap.m.SearchField control. The SearchField should appear slightly different
depending on whether the device on which the app is running has touch capabilities. There's a boolean value controlling the display of the refresh button, and
this is taken from the device model - the named model "device" in the property binding for showRefreshButton. The device model was introduced briefly in
the Component section of this part, and is covered in more detail in the Device Model section. We'll examine the handler for the Search Field shortly, when we
look at the Master controller.
<content>
<List
id="list"
items="{/Products}"
mode="{device>/listMode}"
noDataText="{i18n>masterListNoDataText}"
select="onSelect"

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 19 of 36

growing="true"
growingScrollToLoad="true">
<items>
<ObjectListItem
type="{device>/listItemType}"
press="onSelect"
title="{Name}"
number="{
path: 'Price',
formatter: 'sap.ui.demo.tdg.util.Formatter.currencyValue'
}"
numberUnit="USD">
</ObjectListItem>
</items>
</List>
</content>
The Page's default aggregation is the 'content' aggregation, and contains an sap.m.List control. The List is bound to the "/Products" entity set in our
domain model (more on this in the Data Sources section), and uses growing features to provide paging facilities. The List's selection mode is dependent on
whether the device is a smartphone or not - again, covered by the device model (see later).
With an aggregation binding, a template is required that is used to present each element of the aggregation - in this case each "Product" entity in the domain
model's "/Products" entity set. We declare this in the "items" aggregation declaration - specifying an sap.m.ObjectListItem. Each Product will be
presented using a separate ObjectListItem instance in the List.
Like the List's selection mode, the ObjectListItem's behaviour (via the "type" property) is also device-dependent. We use some of the
ObjectListItem's basic properties to display information for each Product's name and price properties. Because the Northwind service that is being used
for the domain model doesn't have currency information, we're just showing "USD" for this demo.
The Product's price itself needs to be subject to special formatting if it is to look presentable in the app - we would like the values to always appear with two
decimal places. For this, we use a formatter "currencyValue" in the Formatter.js file in our app's util folder. See the "Custom Utilities" section for more
details on how this formatter works. But here, note how the formatter is used, in a complex property binding for the ObjectListItem's number property: the
binding is still introduced with the braces, but is specified as a JavaScript object with a 'path' key pointing to the model property, and a 'formatter' key pointing
to the formatter function.
<footer>
<Bar>
<contentRight>
<Button
icon="sap-icon://add"
tooltip="{i18n>masterFooterAddButtonTooltip}"
press="onAddProduct" />
</contentRight>
</Bar>
</footer>
</Page>
</mvc:View>
Finally we have an sap.m.Bar in the Page's footer aggregation. In this Bar we have an "add" button, on the right, which, when pressed, is to bring up the
AddProduct view for creating a new product. You can see this in the app screenshot in the Introduction section of this part.

Controller
Now we've got the view declaration done, we need to look at the related controller view/Master.controller.js.
jQuery.sap.require("sap.ui.demo.tdg.util.Formatter");
In order to be able to reference and use the currencyValue formatter function (in our util/Formatter.js) for each Product's price property, we need to
declare a requirement for the module. We do it at the top of the view's controller file.
sap.ui.core.mvc.Controller.extend("sap.ui.demo.tdg.view.Master", {
onInit : function() {
this.oUpdateFinishedDeferred = jQuery.Deferred();
this.getView().byId("list").attachEventOnce("updateFinished", function() {
this.oUpdateFinishedDeferred.resolve();
}, this);
In the init event, we create a jQuery Deferred object to be able to know when the update of our List control is finished - in other words, when data has been
loaded. We set the Deferred object to resolved in a handler that we attach to the "updateFinished" event which signals that the binding has been updated.
sap.ui.core.UIComponent.getRouterFor(this).attachRouteMatched(this.onRouteMatched, this);

We then set a handler to handle the routing event.


onRouteMatched : function(oEvent) {
var oList = this.getView().byId("list");
var sName = oEvent.getParameter("name");
var oArguments = oEvent.getParameter("arguments");
In the routing handler, we retrieve and store the event arguments passed, which are the name of the route (the "name" parameter) and the arguments from the
placeholders (in the "arguments") parameter.
// Wait for the list to be loaded once

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 20 of 36

jQuery.when(this.oUpdateFinishedDeferred).then(jQuery.proxy(function() {
var aItems;
// On the empty hash select the first item
if (sName === "main") {
this.selectDetail();
}
// Try to select the item in the list
if (sName === "product") {
aItems = oList.getItems();
for (var i = 0; i < aItems.length; i++) {
if (aItems[i].getBindingContext().getPath() === "/" + oArguments.product) {
oList.setSelectedItem(aItems[i], true);
break;
}
}
}
}, this));
},
It's only when the list has been updated with data do we want to properly handle the route. There are two cases relating to whether a product has been selected
via the hash or not. The "main" route will match where there's no hash, and the "product" route will match with a hash containing product (and optional supplier
or category tab) information. If there's no product selected, we just select the first item. Otherwise we try to find the selected product in the list and set that to
be visibly selected.
selectDetail : function() {
if (!sap.ui.Device.system.phone) {
var oList = this.getView().byId("list");
var aItems = oList.getItems();
if (aItems.length && !oList.getSelectedItem()) {
oList.setSelectedItem(aItems[0], true);
this.showDetail(aItems[0]);
}
}
},
The selectDetail function checks to see if an item has already been selected (this could have happened based on the route match handling above) and if
so, makes sure that the item's details are shown in the Detail view, via the showDetail function.
onSearch : function() {
// add filter for search
var filters = [];
var searchString = this.getView().byId("searchField").getValue();
if (searchString && searchString.length > 0) {
filters = [ new sap.ui.model.Filter("Name", sap.ui.model.FilterOperator.Contains, searchString) ];
}
// update list binding
this.getView().byId("list").getBinding("items").filter(filters);
},
This function is the handler for the SearchField in the Master view ( <SearchField ... search="onSearch" ... /> ). This is where some of the real
power of the ODataModel is to be seen. We retrieve the search text entered, and create a "Contains" filter, in the form of an sap.ui.model.Filter, to
search for that text. And by applying this Filter (within an array of possible Filters) to the List's "items" binding, an OData QUERY operation is performed
automatically via the ODataModel mechanism. Here's an example of what that request might look like, with a search term of "Lemon":

GET
http://<host>:<port>/uilib-sample/proxy/http/services.odata.org/V2/(S(sapuidemotdg))/OData/OData.svc/Products?$skip=0&$top=1&$filt
Note that the $top=1 was included as it the number of entities containing "Lemon" in the name was determined as being 1, according to an also automatic
request for a count:

GET
http://<host>:<port>/uilib-sample/proxy/http/services.odata.org/V2/(S(sapuidemotdg))/OData/OData.svc/Products?$count?$filter=subst
This automatic request for a count is based on the fact that this domain model is specified as supporting the OData $count by default. You can turn this off
with sap.ui.model.odata.ODataModel.setCountSupported function.
onSelect : function(oEvent) {
// Get the list item, either from the listItem parameter or from the event's
// source itself (will depend on the device-dependent mode).
this.showDetail(oEvent.getParameter("listItem") || oEvent.getSource());
},
At some stage, the user will select an item, either via the List itself, or via one of the aggregated ObjectListItems. Which one the user will select, depends on
the device model settings. We handle both events (one on the List, the other on the ObjectListItem) with the onSelect function.
We're looking for the control instance that has the appropriate data context. If the event is triggered from the List, then the event parameter listItem is made
available, and that's the context. Otherwise the context is the control itself that's raising the event - i.e. the individual ObjectListItem bound to the specific
entity in the Products entity set. In both cases we pass this object to the showDetail function.
showDetail : function(oItem) {
// If we're on a phone, include nav in history; if not, don't.

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 21 of 36

var bReplace = jQuery.device.is.phone ? false : true;


sap.ui.core.UIComponent.getRouterFor(this).navTo("product", {
from: "master",
product: oItem.getBindingContext().getPath().substr(1),
tab: "supplier"
}, bReplace);
},
The showDetail function is called from the onSelect event handler to show the detail of the selected item in this Master view. It does this by using the
Router to navigate to the "product" view (this is the name from the routing configuration in the Component).
Note how we pass the information as to which product detail is to be shown: the two parameter names "product" and "tab" from the pattern "
{product}/:tab: " in the "product" subroute definition are specified with appropriate values:
"product" gets the value of the path of the selected item's binding context, minus the leading slash (e.g. "/Products(7)" -> "Products(7)")
"tab" gets the default value "supplier" so that the initial entry in the Detail view's IconTabBar is pre-selected
onAddProduct : function() {
sap.ui.core.UIComponent.getRouterFor(this).myNavToWithoutHash({
currentView : this.getView(),
targetViewName : "sap.ui.demo.tdg.view.AddProduct",
targetViewType : "XML",
transition : "slide"
});
}
});
Finally we must handle the "add" button in the Page's footer Bar. In this case we just need to get the Router to put the AddProduct view into place. This uses
our custom routing function sap.ui.demo.tdg.MyRouter.myNavToWithoutHash to
load the view
add it to the appropriate aggregation in the SplitApp (masterPages or detailPages)
navigate to it

Progress Check
Now our app folder content now looks like this:
tdg/
|
+-|
|
|
+-|
|
|
|
|
|
+-+-+--

i18n/
|
+-- messageBundle.properties
view/
|
+-+-+-+--

App.view.xml
Detail.view.xml
Master.controller.js
Master.view.xml

Component.js
index.html
MyRouter.js

We now have something to show that's starting to look like our app!

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 22 of 36

1.9 Step 9: Detail View


We'll now add the Detail view, replacing the skeleton placeholder we had up until now.

View
Here's what the view declaration looks like.
<mvc:View
controllerName="sap.ui.demo.tdg.view.Detail"
xmlns:mvc="sap.ui.core.mvc"
xmlns:core="sap.ui.core"
xmlns="sap.m">
Like the Master view we specify that this view has a controller, which we'll get to shortly, and that the default namespace is sap.m. While the "mvc"
namespace is for the outermost View element itself, unlike the Master view we also have a "core" namespace (sap.ui.core), which is needed in this view
because we're going to pull in a couple of XML fragments using sap.ui.core.Fragment.
<Page
showNavButton="{device>/isPhone}"
navButtonPress="onNavBack"
class="sapUiFioriObjectPage"
title="{i18n>detailTitle}">
<content>
Also like the Master view, this view's main control is a Page. This time, however, we may or may not need a back button. Remember that we may be running
on a smartphone, or a tablet, or a desktop.
If we're running on a tablet or a desktop, the app will look like what you see in the screenshot, with the master and detail parts of the SplitApp visible at the
same time. So the user has no difficulty navigating to another Product in the master list. But on a smartphone, only either the master or the detail part is
displayed at any given time, meaning that if the user is viewing a detail page and wants to select another product, they first need to get to the master. This is
what the navigation button is for - to go back. And we only want it to be shown if the device is a smartphone, so we use the Device Model and a boolean flag
to set on the showNavButton property of the Page.
A Page control's default aggregation is 'content'; while we therefore don't need to specify the <content> tag here, we should do as it's good practice.
<ObjectHeader
title="{Name}"
number="{
path: 'Price',
formatter: 'sap.ui.demo.tdg.util.Formatter.currencyValue'
}"
numberUnit="USD">
<attributes>
<ObjectAttribute text="{i18n>detailFromDate} {
path: 'ReleaseDate',
type: 'sap.ui.model.type.Date'
}" />
<ObjectAttribute text="{Description}" />
</attributes>
<firstStatus>
<ObjectStatus
text="{

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 23 of 36

path: 'DiscontinuedDate',
formatter: 'sap.ui.demo.tdg.util.Formatter.discontinuedStatusValue'
}"
state="{
path: 'DiscontinuedDate',
formatter: 'sap.ui.demo.tdg.util.Formatter.discontinuedStatusState'
}" />
</firstStatus>
</ObjectHeader>
The detail consists of an sap.m.ObjectHeader control at the top. This has a couple of aggregations that are being used - attributes and
firstStatus. For all of the properties in the controls that are aggregated (such as sap.m.ObjectAttribute and sap.m.ObjectStatus), as well as
some of the basic properties (such as 'number') we use the extended binding syntax again to enable us to use formatter and type specifications.
While the formatter functions were introduced in the Custom Utilities section, it's worth highlighting here that formatter functions are not just for returning
formatted versions of the values that are passed to them. They can be used, as here in the state property of the ObjectStatus control, to return any value, in
this case one of the enumerated states in sap.ui.core.ValueState, to highlight a discontinued product status with a semantic color - red, via the Error
state.
<IconTabBar
select="onDetailSelect"
id="idIconTabBar">
<items>
<IconTabFilter
key="supplier"
text="{i18n>iconTabFilterSupplier}"
icon="sap-icon://supplier">
<content>
<!--core:Fragment fragmentName="sap.ui.demo.tdg.view.SupplierAddressForm" type="XML" /-->
</content>
</IconTabFilter>
<IconTabFilter
key="category"
text="{i18n>iconTabFilterCategory}"
icon="sap-icon://hint">
<content>
<!--core:Fragment fragmentName="sap.ui.demo.tdg.view.CategoryInfoForm" type="XML" /-->
</content>
</IconTabFilter>
</items>
</IconTabBar>
The other main part of the detail view's content is an sap.m.IconTabBar, to display information about the selected Product entity's Supplier and Category
information.
The two sap.m.IconTabFilter controls that are contained in the IconTabBar reference XML fragments where the bulk of the information layout is
declared. We'll come to the content of these XML fragments later. For now, the references ("core:Fragment...") are temporarily commented out, because
if we include them now without adding the actual XML fragment files, we'll get an error and our progress will take a few steps backwards. Note the key
properties of each IconTabFilter - we'll be using these to control the navigation to the appropriate information display and data binding in the
RouteMatched handler in the controller.
</content>
<footer>
<Bar>
</Bar>
</footer>
</Page>
</mvc:View>
To finish off the Page, we have an sap.m.Bar in the footer aggregation, so that the detail view matches the master visually.

Controller
The bulk of the detail view's controller is for handling the navigation events that are initiated through the Router.
sap.ui.core.mvc.Controller.extend("sap.ui.demo.tdg.view.Detail", {
onInit : function() {
var oView = this.getView();
sap.ui.core.UIComponent.getRouterFor(this).attachRouteMatched(function(oEvent) {
// when detail navigation occurs, update the binding context
if (oEvent.getParameter("name") === "product") {
var sProductPath = "/" + oEvent.getParameter("arguments").product;
oView.bindElement(sProductPath);
// Check that the product specified actually was found
oView.getElementBinding().attachEventOnce("dataReceived", jQuery.proxy(function() {
var oData = oView.getModel().getData(sProductPath);
if (!oData) {
sap.ui.core.UIComponent.getRouterFor(this).myNavToWithoutHash({
currentView : oView,

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 24 of 36

targetViewName : "sap.ui.demo.tdg.view.NotFound",
targetViewType : "XML"
});
}
}, this));

// Make sure the master is here


var oIconTabBar = oView.byId("idIconTabBar");
oIconTabBar.getItems().forEach(function(oItem) {
oItem.bindElement(sap.ui.demo.tdg.util.Formatter.uppercaseFirstChar(oItem.getKey()));
});
// Which tab?
var sTabKey = oEvent.getParameter("arguments").tab || "supplier";
if (oIconTabBar.getSelectedKey() !== sTabKey) {
oIconTabBar.setSelectedKey(sTabKey);
}
}
}, this);
},
This RouteMatched event handler has been covered already in the Navigation and Routing section, but it's worth revisiting and looking at a couple of details.
We determine the path of the chosen Product (in sProductPath) and perform an element binding to this on the detail view. Further, we bind each
IconTabFilter (the items in the IconTabBar) to the entity that directly relates to the key of that IconTabFilter, i.e. Supplier or Category.
Note that we also attach a handler to the dataReceived event to check that data was actually received for this binding - and if not we ask the Router to
navigate to the NotFound view.
We're not making an explicit call to navigate from the selected Product to the associated Supplier or Category, the Supplier and Category details are not
contained within the Product entity, and we haven't tried to use the OData $expand in the original Product entity set binding to pull each associated entity in
with the original call (that would be retrieving too much data perhaps unnecessarily). So what's happening here?
What's happening is that the sap.ui.model.odata.ODataModel mechanism is handling the navigation from entity to entity for us automatically, following
the navigation properties that are described in the OData service metadata thus:
<EntityType Name="Product">
<Key>
<PropertyRef Name="ProductID" />
</Key>
<Property Name="ProductID" ... />
...
<NavigationProperty Name="Category" ... />
<NavigationProperty Name="Supplier" ... />
</EntityType>
The automatic navigation also causes the appropriate OData operations to be invoked implicitly. You can see this in the browser's developer console in the
Network tab: two GET requests are made:
GET
http://localhost:8080/uilib-sample/proxy/http/services.odata.org/V2/(S(sapuidemotdg))/OData/OData.svc/Products(2)/Supplier
GET
http://localhost:8080/uilib-sample/proxy/http/services.odata.org/V2/(S(sapuidemotdg))/OData/OData.svc/Products(2)/Supplier
So - you don't need to "drive" the ODataModel to navigate your service; embrace it and let it do the hard work for you!
onNavBack : function() {
// This is only relevant when running on phone devices
sap.ui.core.UIComponent.getRouterFor(this).myNavBack("main");
},
onDetailSelect : function(oEvent) {
sap.ui.core.UIComponent.getRouterFor(this).navTo("product",{
product : oEvent.getSource().getBindingContext().getPath().slice(1),
tab: oEvent.getParameter("selectedKey")
}, true);
}
});
There are a couple of events that will be raised in the UI, and they need to be handled.
The first is when the Page's navigation button (if shown) is pressed. In this case, the onNavBack function is invoked, and this uses the Router to navigate
back to the Master view via the "main" route (defined earlier - see the Component section).There's also an event that's raised when an sap.m.IconTabBar
item is selected. In this case we just use the Router again to move us to the appropriate sap.m.IconTabFilter; in calling the Router's navTo function we
pass the name of the Product (from the binding context path) and the key of the IconTabFilter that was selected.

Progress Check
With our skeleton Detail.view.xml file replaced, and a corresponding controller added, our app folder content now looks like this:
tdg/
|

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 25 of 36

+-|
|
|
+-|
|
|
+-|
|
|
|
|
|
|
+-+-+--

i18n/
|
+-- messageBundle.properties
util/
|
+-- Formatter.js
view/
|
+-+-+-+-+--

App.view.xml
Detail.controller.js
Detail.view.xml
Master.controller.js
Master.view.xml

Component.js
index.html
MyRouter.html

When a Product is selected, this is how it appears:

The IconTabFilters for the Supplier and Category information are empty, but that's just because we haven't got round to including the XML fragments that
describe what goes in there. Let's get to that next.

1.10 Step 10: Detail XML Fragments


In the Detail View section, we referenced XML fragments in the sap.m.IconTabFilters. These fragments are separately maintainable and reusable
collections of control declarations.
In both cases, for the Supplier and Category information display, we use the sap.ui.layout.form.SimpleForm. This offers a flexible approach to
building responsive form layouts that automatically adjust according to screen size.
Because of the way Fragments are instantiated and inserted, the binding context is inherited according to the insertion point. So in the case of both of the XML
fragments that we have for our app, the binding context is relative to the binding already on the detail view.
Here are the XML fragments. Both are held in their own files, and are normally kept in the same folder as the views and controllers.

SupplierAddressForm
First up is the SupplierAddressForm fragment, in file SupplierAddressForm.fragment.xml.
<core:FragmentDefinition
xmlns:l="sap.ui.layout"
xmlns:f="sap.ui.layout.form"
xmlns:core="sap.ui.core"
xmlns="sap.m">
This outermost element core:FragmentDefinition is not strictly necessary here, as our control definition has a single root node l:Grid. However, it's
good practice to always use this core:FragmentDefinition element as a container, not least because then any additional root node in the definition
doesn't cause a bigger change during maintenance. The container element doesn't have any representation in the HTML.
<l:Grid

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 26 of 36

defaultSpan="L12 M12 S12"


width="auto">
<l:content>
<f:SimpleForm
minWidth="1024"
maxContainerCols="2"
editable="false"
layout="ResponsiveGridLayout"
title="{i18n>supplierAddress}"
labelSpanL="3"
labelSpanM="3"
emptySpanL="4"
emptySpanM="4"
columnsL="1"
columnsM="1">
<f:content>
<Label text="{i18n>supplierAddressName}" />
<Text text="{Name}" />
<Label text="{i18n>supplierAddressStreet}" />
<Text text="{Address/Street}" />
<Label text="{i18n>supplierAddressCity}" />
<Text text="{Address/City}" />
<Label text="{i18n>supplierAddressZIPCode}" />
<Text text="{Address/ZipCode}" />
<Label text="{i18n>supplierAddressCountry}" />
<Text text="{Address/Country}" />
</f:content>
</f:SimpleForm>
</l:content>
</l:Grid>
</core:FragmentDefinition>
The SimpleForm's layout is a series of sap.m.Label and sap.m.Text controls. Note that the Supplier address is defined as a ComplexType in the OData
service and this type is used in the definition of the Supplier entity:
<Schema Namespace="ODataDemo" ...>
<EntityType Name="Supplier">
<Key>
<PropertyRef Name="ID" />
</Key>
<Property Name="ID" Type="Edm.Int32" ... />
<Property Name="Name" Type="Edm.String" ... />
<Property Name="Address" Type="ODataDemo.Address" ... />
...
</EntityType>
...
<ComplexType Name="Address">
<PropertyRef Name="Street" ... />
<PropertyRef Name="City" ... />
...
</EntityType>
...
</Schema>
This means that we can refer to the address details for a Supplier entity via a binding path that includes an "Address/" prefix, as seen in the bindings for the
text properties of the address-relevant Text controls.

CategoryInfoForm
Here's the CategoryInfoForm fragment.
<core:FragmentDefinition
xmlns:l="sap.ui.layout"
xmlns:f="sap.ui.layout.form"
xmlns:core="sap.ui.core"
xmlns="sap.m">
<l:Grid
defaultSpan="L12 M12 S12"
width="auto">
<l:content>
<f:SimpleForm
minWidth="1024"
maxContainerCols="2"
editable="false"
layout="ResponsiveGridLayout"
title="{i18n>categoryInfo}"
labelSpanL="3"
labelSpanM="3"

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 27 of 36

emptySpanL="4"
emptySpanM="4"
columnsL="1"
columnsM="1">
<f:content>
<Label text="{i18n>categoryInfoID}" />
<Text text="{ID}" />
<Label text="{i18n>categoryInfoName}" />
<Text text="{Name}" />
</f:content>
</f:SimpleForm>
</l:content>
</l:Grid>
</core:FragmentDefinition>
It's very similar to the SupplierAddressForm fragment. Again, the bindings for the Text controls are relative to where the fragment is inserted; in this case it will
be relative to the associated Category.

Progress Check
With the fragment files added to the view subfolder, our app folder content now looks like this:
tdg/
|
+-|
|
|
+-|
|
|
+-|
|
|
|
|
|
|
|
|
+-+-+--

i18n/
|
+-- messageBundle.properties
util/
|
+-- Formatter.js
view/
|
+-+-+-+-+-+-+--

App.view.xml
CategoryInfoForm.fragment.xml
Detail.controller.js
Detail.view.xml
Master.controller.js
Master.view.xml
SupplierAddressForm.fragment.xml

Component.js
index.html
MyRouter.html

And when we uncomment the fragment insertion points in the detail view (see the Detail View section)
<items>
<IconTabFilter
key="supplier"
text="{i18n>iconTabFilterSupplier}"
icon="sap-icon://supplier">
<content>
<core:Fragment fragmentName="sap.ui.demo.tdg.view.SupplierAddressForm" type="XML" />
</content>
</IconTabFilter>
<IconTabFilter
key="category"
text="{i18n>iconTabFilterCategory}"
icon="sap-icon://hint">
<content>
<core:Fragment fragmentName="sap.ui.demo.tdg.view.CategoryInfoForm" type="XML" />
</content>
</IconTabFilter>
</items>
our app now looks like this, when a product is selected (remember, the default IconTabFilter is the for the supplier information):

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 28 of 36

And it looks like this when the category IconTabFilter is selected:

1.11 Step 11: AddProduct View


Let's round out the core functionality of the app by bringing in the facility to add a new product.
We've already got the "add" button in the footer of the Page in the master view, but right now, when we press it, predictably, nothing happens visually, and we
get this error in the console:
Uncaught Error: resource sap/ui/demo/tdg/view/AddProduct.view.xml could not be loaded from
./view/AddProduct.view.xml. Check for 'file not found' or parse errors.
So let's add the view and controller.

View
Let's first take a peek at what we're trying to achieve in this view definition.

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 29 of 36

It's an sap.m.Page, with an sap.ui.layout.form.SimpleForm within the flexible sap.ui.layout.Grid control. Like the Page controls in the master
and detail views, this Page also has a Bar in the footer, and in this case, there are two buttons on the right hand side.
<mvc:View
controllerName="sap.ui.demo.tdg.view.AddProduct"
xmlns:mvc="sap.ui.core.mvc"
xmlns:l="sap.ui.layout"
xmlns:f="sap.ui.layout.form"
xmlns:c="sap.ui.core"
xmlns="sap.m">
This should be familiar by now; we're declaring our view with the namespaces for the controls we're going to use within.
<Page
class="sapUiFioriObjectPage"
title="{i18n>addProductTitle}">
<l:Grid
defaultSpan="L12 M12 S12"
width="auto">
<l:content>
<f:SimpleForm
id="idAddProductForm"
minWidth="800"
maxContainerCols="2"
editable="true"
layout="ResponsiveGridLayout"
title="New Product"
labelSpanL="3"
labelSpanM="3"
emptySpanL="4"
emptySpanM="4"
columnsL="1"
columnsM="1"
class="editableForm">
<f:content>
The SimpleForm sits within the Grid. The SimpleForm's content aggregation is where we define our separate form sections. Each section begins with an
sap.ui.core.Title control, which causes a subheader style title to appear (these are the Basic Info , Discontinued and Supplier & Category texts
visible in the screenshot above).
<!-- Basic info -->
<c:Title text="{i18n>addProductTitleBasic}" />
<Label text="{i18n>addProductLabelName}" />
<Input value="{newProduct>/Detail/Name}" />
<Label text="{i18n>addProductLabelDescription}" />
<TextArea value="{newProduct>/Detail/Description}" />
<Label text="{i18n>addProductLabelReleaseDate}" />
<DateTimeInput
type="Date"
value="{newProduct>/Detail/ReleaseDate}" />
<Label text="{i18n>addProductLabelPrice}" />
<Input value="{newProduct>/Detail/Price}" />
<Label text="{i18n>addProductLabelRating}" />

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 30 of 36

<Label text="{i18n>addProductLabelRating}" />


<RatingIndicator
visualMode="Full"
value="{newProduct>/Detail/Rating}" />
To collect the data in the form fields, and have access to it in the controller, we're using a named client side model - an sap.ui.model.json.JSONModel
to be precise. The model's name is newProduct and we can see the prefix in use in the property bindings such as {newProduct>/Detail/Name}. The
model is instantiated and set on this view in the init event (see the details on the controller below).
In the OData service, the Rating property of the Product entity id defined as an integer ( <Property Name="Rating" Type="Edm.Int32"
Nullable="false"/> ) and therefore we set the visualMode property of the sap.m.RatingIndicator control to "Full" to cause the values to be
rounded to the nearest integer value.
<!-- Discontinued? -->
<c:Title text="{i18n>addProductTitleDiscontinued}" />
<Label text="{i18n>addProductLabelDiscontinuedFlag}" />
<CheckBox selected="{newProduct>/Detail/DiscontinuedFlag}" />
<Label
visible="{newProduct>/Detail/DiscontinuedFlag}"
text="{i18n>addProductLabelDiscontinuedDate}" />
<DateTimeInput
type="Date"
visible="{newProduct>/Detail/DiscontinuedFlag}"
value="{newProduct>/Detail/DiscontinuedDate}" />
This section is similar to the Basic Info section of the form. Noteworthy, however, is the use of the Detail/DiscontinuedFlag property in the
newProduct JSON model. It's set to false by default, and is used to set the visibility of the sap.m.Label and sap.m.DateTimeInput controls for the
DiscontinuedDate value. So only if the Discontinued checkbox is checked does the extra label and field for the date appear. This is done just with
property bindings on a model, without controller logic.
<!-- Supplier & Category -->
<c:Title text="{i18n>addProductTitleSupplierCategory}" />
<Label text="{i18n>addProductLabelSupplier}" />
<Select
id="idSelectSupplier"
items="{/Suppliers}"
width="100%">
<c:Item text="{Name}" />
</Select>
<Label text="{i18n>addProductLabelCategory}" />
<Select
id="idSelectCategory"
items="{/Categories}"
width="100%">
<c:Item text="{Name}" />
</Select>
</f:content>
</f:SimpleForm>
</l:content>
</l:Grid>
The last form section is a pair of sap.m.Select controls, each bound to entity sets in the ODataModel so that a Supplier and a Category can be chosen for
the new Product. Note that in this case, the binding path is absolute, because there's no binding in this form that would be relevant for any relative connection.
<footer>
<Bar>
<contentRight>
<Button text="{i18n>addProductButtonSave}" type="Emphasized" press="onSave" />
<Button text="{i18n>addProductButtonCancel}" press="onCancel" />
</contentRight>
</Bar>
</footer>
</Page>
</mvc:View>
Finally, there are the two sap.m.Button controls in the footer's Bar. The action button ( Save ) is highlighted using the Emphasized type from the
sap.m.ButtonType enumeration.

Controller
sap.ui.core.mvc.Controller.extend("sap.ui.demo.tdg.view.AddProduct", {
oAlertDialog : null,
oBusyDialog : null,
We're going to show an alert dialog if there's a problem with the input, and we also have an sap.m.BusyDialog to show while the process of saving the new
product is carried out. We'll hold references to these here.

Note
In this demo app, there is little to no client side validation. That is left as an exercise for you, dear reader!
initializeNewProductData : function() {
this.getView().getModel("newProduct").setData({
Detail: {
DiscontinuedFlag: false

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 31 of 36

}
});
},
With this initializeNewProductData function, we can reset the data in the JSON model, effectively clearing the values in the form fields. We're holding
the properties within a single member "Detail" to make it simpler to reset.
onInit : function() {
this.getView().setModel(new sap.ui.model.json.JSONModel(), "newProduct");
this.initializeNewProductData();
},
This is the named model that is used to communicate data between the form in the view and controller.
showErrorAlert : function(sMessage) {
jQuery.sap.require("sap.m.MessageBox");
sap.m.MessageBox.alert(sMessage);
},
Later, we instantiate an sap.m.Dialog control to alert the user to issues with form input. But not all alerts need to use explicitly instantiated controls; in this
showErrorAlert function, invoked in a couple of places in this controller, we use the static method sap.m.MessageBox.alert as a convenience.
dateFromString : function(sDate) {
// Try to create date directly, otherwise assume dd/mm/yyyy
var oDate = new Date(sDate);
return oDate === "Invalid Date" ? new Date(sDate.split("/").reverse()) : oDate;
},
saveProduct : function(nID) {
var mNewProduct = this.getView().getModel("newProduct").getData().Detail;
// Basic payload data
var mPayload = {
ID: nID,
Name: mNewProduct.Name,
Description: mNewProduct.Description,
ReleaseDate: this.dateFromString(mNewProduct.ReleaseDate),
Price: mNewProduct.Price.toString(),
Rating: mNewProduct.Rating
};
if (mNewProduct.DiscontinuedDate) {
mPayload.DiscontinuedDate = this.dateFromString(mNewProduct.DiscontinuedDate);
}
The saveProduct function does the heavy lifting of invoking the OData CREATE operation on the OData service via the domain model. First, the data in the
JSON model is retrieved and unpacked, and a payload for the CREATE operation is created.
// Add supplier & category associations
["Supplier", "Category"].forEach(function(sRelation) {
var oSelect = this.getView().byId("idSelect" + sRelation);
var sPath = oSelect.getSelectedItem().getBindingContext().getPath();
mPayload[sRelation] = {
__metadata: {
uri: sPath
}
};
}, this);
Remember that the Product entity has associations to the Supplier and Category entities. So when we create the new Product, we must ensure that the
associations are made to the Supplier and Category chosen in the form. This is done by specifying a __metadata object for each association, with a uri
property pointing to the chosen entity's path.
Here's an example of what that payload looks like:
{
"ID":9,
"Name":"Galaxy S4",
"Description":"Samsung smartphone",
"ReleaseDate":null,
"Price":"499.00",
"Rating":4,
"Supplier":{
"__metadata":{
"uri":"/Suppliers(1)"
}
},
"Category":{
"__metadata":{
"uri":"/Categories(2)"
}
}
}
This example is for a new product that is associated with the supplier Tokyo Traders (/Suppliers(1)) and category "Electronics" (/Categories(2)).
// Send OData Create request
var oModel = this.getView().getModel();
oModel.create("/Products", mPayload, {

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 32 of 36

success : jQuery.proxy(function(mResponse) {
this.initializeNewProductData();
sap.ui.core.UIComponent.getRouterFor(this).navTo("product", {
from: "master",
product: "Products(" + mResponse.ID + ")",
tab: "supplier"
}, false);
jQuery.sap.require("sap.m.MessageToast");
// ID of newly inserted product is available in mResponse.ID
this.oBusyDialog.close();
sap.m.MessageToast.show("Product '" + mPayload.Name + "' added");
}, this),
error : jQuery.proxy(function() {
this.oBusyDialog.close();
this.showErrorAlert("Problem creating new product");
}, this)
});
},
Once the payload is ready the OData CREATE request is invoked. On success, a MessageToast is shown with the ID of the newly created Product entity,
the form data is reset, and we get the Router to navigate the user to the display of the new Product entry. Otherwise a simple alert message is shown using
the showAlertError function that we've already seen.
onSave : function() {
// Show message if no product name has been entered
// Otherwise, get highest existing ID, and invoke create for new product
if (!this.getView().getModel("newProduct").getProperty("/Detail/Name")) {
if (!this.oAlertDialog) {
this.oAlertDialog = sap.ui.xmlfragment("sap.ui.demo.tdg.view.NameRequiredDialog", this);
this.getView().addDependent(this.oAlertDialog);
}
this.oAlertDialog.open();
We impose a small restriction on the input of the new product details - there must be a name specified. If not, we instantiate an alert Dialog (if it doesn't exist
already) and show it. Where is the definition? In an XML fragment, of course!
Fragments are not only useful for for separating out UI definitions into discrete and maintainable chunks, but also allow you to use your choice of view
definition language (in our case XML) for all of your UI elements. So instead of declaring the sap.m.Dialog-based alert in-line, in this condition, with
JavaScript, you can and should declare it using the same approach (XML) as the rest of your UI. See below for the XML fragment where this Dialog is defined.
} else {
if (!this.oBusyDialog) {
this.oBusyDialog = new sap.m.BusyDialog();
}
this.oBusyDialog.open();
this.getView().getModel().read("/Products", {
urlParameters : {
"$top" : 1,
"$orderby" : "ID desc",
"$select" : "ID"
},
async : false,
success : jQuery.proxy(function(oData) {
this.saveProduct(oData.results[0].ID + 1);
}, this),
error : jQuery.proxy(function() {
this.oBusyDialog.close();
this.showErrorAlert("Cannot determine next ID for new product");
}, this)
});
}
},
If all is ok with the form input, we want to go ahead with saving the new product. Our OData service (Northwind) requires an ID key to be specified (it's not
auto-generated on creation). So we need to find the highest ID and then increment it.For the first time in this app, we're going to invoke an explicit OData
operation to retrieve data: a QUERY, via the sap.ui.model.odata.ODataModel.read function. For READ and QUERY operations on an OData service,
this should be the exception. But in this case it's required - we need to use a combination of the $top, $orderby and $select query options: "Give me the
ID property of the entity that has the highest ID in the Products entity set"
onCancel : function() {
sap.ui.core.UIComponent.getRouterFor(this).backWithoutHash(this.getView());
},
This function is called to handle the press event of the "Cancel" Button. It uses the sap.ui.demo.tdg.MyRouter.backWithoutHash function to return
to the product detail that was visible before this AddProduct view was displayed. This backWithoutHash function recursively ascends the UI tree via a
helper function _findSplitApp, to discover the app's "root' control, the sap.m.SplitApp. On finding this, it can call the appropriate back function.
onDialogClose : function(oEvent) {
oEvent.getSource().getParent().close();
}
});
The last function in this controller is a handler for the press event on the sap.m.Dialog defined in the XML fragment

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 33 of 36

NameRequiredDialog.fragment.xml.

Fragment
The XML fragment NameRequiredDialog.fragment.xml is where the alert Dialog is defined, and looks like this:
<core:FragmentDefinition
xmlns:core="sap.ui.core"
xmlns="sap.m">
<Dialog
title="{i18n>nameRequiredDialogTitle}"
type="Message">
<content>
<Text text="{i18n>nameRequiredDialogText}" />
</content>
<beginButton>
<Button text="{i18n>nameRequiredDialogButton}" press="onDialogClose" />
</beginButton>
</Dialog>
</core:FragmentDefinition>
Like the other fragments in this app (see the Detail XML Fragments section) this fragment has only a single root node ( <Dialog> ), nevertheless we're using
the <core:FragmentDefinition> wrapper here too. Note also that we're also explicitly specifying the sap.m.Dialog's default aggregation rather than
implying it ( <content> ). Finally, it's worth pointing out that the handler for the press event is to be found in the controller related to the view where this
fragment is inserted, i.e. AddProduct.controller.js.

Progress Check
Ok, we're almost there. Our app folder content now looks like this:
tdg/
|
+-|
|
|
+-|
|
|
+-|
|
|
|
|
|
|
|
|
|
|
|
+-+-+--

i18n/
|
+-- messageBundle.properties
util/
|
+-- Formatter.js
view/
|
+-+-+-+-+-+-+-+-+-+--

AddProduct.controller.js
AddProduct.view.xml
App.view.xml
CategoryInfoForm.fragment.xml
Detail.controller.js
Detail.view.xml
Master.controller.js
Master.view.xml
NameRequiredDialog.fragment.xml
SupplierAddressForm.fragment.xml

Component.js
index.html
MyRouter.html

Here's a shot of the AddProduct view with the alert Dialog shown (as we have deliberately not specified a value for the Product Name:

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 34 of 36

1.12 Step 12: NotFound View


There's one more thing that we need to finish off. In the Router configuration (see the Navigation and Routing section) there's a route definition for NotFound .
Navigation to this is invoked in the Detail view's controller (see the Detail View section). So we need to create this view so that the Router can load it, add it to
the SplitApp, and navigate to it.
This is the content of NotFound.view.xml in the view subfolder:
<mvc:View
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m">
<Page
class="sapUiFioriObjectPage"
title="{i18n>notFoundTitle}">
<content>
<FlexBox
alignItems="Center"
justifyContent="Center">
<items>
<Label
class="spacerTop"
textAlign="Center"
text="{i18n>notFoundText}" />
</items>
</FlexBox>
</content>
<footer>
<Bar>
</Bar>
</footer>
</Page>
</mvc:View>
It's a simple affair. To fit in with the app's overall look, we're using an sap.m.Page control with a title and a Bar in the footer. The Page's default content
aggregation contains an sap.m.FlexBox control with an sap.m.Label control centrally aligned and justified. The Label has a CSS class defined so we can
use some custom CSS to move the text in the Label's text property down a little bit.
Here's the custom CSS, which is stored in style.css in the css subfolder:
.spacerTop {
margin-top: 2rem;
}
In order for this custom CSS to work, you first need to write the following line of code inside your index.html file:
<link rel="stylesheet" type="text/css" href="css/style.css">

Progress Check
Finally, this is our complete app folder:
tdg/
|

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 35 of 36

+-|
|
|
+-|
|
|
+-|
|
|
+-|
|
|
|
|
|
|
|
|
|
|
|
|
+-+-+--

css/
|
+-- style.css
i18n/
|
+-- messageBundle.properties
util/
|
+-- Formatter.js
view/
|
+-+-+-+-+-+-+-+-+-+-+--

AddProduct.controller.js
AddProduct.view.xml
App.view.xml
CategoryInfoForm.fragment.xml
Detail.controller.js
Detail.view.xml
Master.controller.js
Master.view.xml
NameRequiredDialog.fragment.xml
NotFound.view.xml
SupplierAddressForm.fragment.xml

Component.js
index.html
MyRouter.html

When navigating to an unknown URL, this is what the user will see:

PUBLIC
2014 SAP SE or an SAP affiliate company. All rights reserved.

Page 36 of 36

Das könnte Ihnen auch gefallen