OpenUI5 - SAP Help Portal

Developer Guide
Document Version: 1.52 – 2018-01-31
OpenUI5: UI Development Toolkit for HTML5
Content
OpenUI5: UI Development Toolkit for HTML5. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
What's New in OpenUI5. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
What's New in OpenUI5 1.52. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
What's New in OpenUI5 1.50. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .12
Read Me First. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .17
Compatibility Rules. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Browser and Platform Support. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Supported Library Combinations. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Supported Combinations of Themes and Libraries. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .28
Versioning of OpenUI5. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Upgrading. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Deprecated Themes and Libraries. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
SAPUI5 vs. OpenUI5. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Get Started: Setup and Tutorials. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
Development Environment. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .41
“Hello World!”. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
Walkthrough. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
Troubleshooting. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
Data Binding. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
Navigation and Routing. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
Testing. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .344
Mock Server. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 409
Worklist App. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 421
Demo Apps. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 465
Essentials. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 472
Bootstrapping: Loading and Initializing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 473
Structuring: Components and Descriptor. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 501
Model View Controller (MVC). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 559
Data Binding. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .673
Reusing UI Parts: Fragments. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 733
HTML and XML Templating. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 747
Working with Controls. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 771
Declarative Support. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 785
Managing UI and Server Messages. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 791
Routing and Navigation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 797
Optimizing Applications. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 811
2
OpenUI5: UI Development Toolkit for HTML5
Content
Adapting to Operating Systems And Devices. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 827
OpenUI5 Accessibility Features. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .841
Localization. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 846
Testing. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .852
Troubleshooting. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 924
Theming. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 994
Developing Apps. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1008
Continuous Integration: Ensure Code Quality . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1010
App Templates: Kick Start Your App Development. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1011
App Overview: The Basic Files of Your App. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1038
App Initialization: What Happens When an App Is Started?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1040
Folder Structure: Where to Put Your Files. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1041
Device Adaptation: Using Device Models for Your App. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1046
Performance: Speed Up Your App. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1047
Stable IDs: All You Need to Know. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1050
Coding Issues to Avoid. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1054
Securing Apps. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1065
Right-to-Left Support. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1077
Accessibility. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1079
Extending Apps. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1102
Example: Component Configuration. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1102
View Extension. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1104
View Modification. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1106
View Replacement. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1107
Controller Extension. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1108
Controller Replacement. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1110
Localized Texts for Extended Apps. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1111
Limitations. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1112
Caveats Regarding Stability Across Application Upgrades. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1112
Supportability. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1113
Developing Content. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1114
Development Conventions and Guidelines. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1115
Developing OpenUI5 Controls. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1138
More About Controls. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1197
Busy Indicators. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1198
Semantic Pages. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1199
Tables: Which One Should I Choose?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1208
sap.f. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1209
sap.m. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1218
sap.tnt. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1289
sap.ui.codeeditor. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1291
OpenUI5: UI Development Toolkit for HTML5
Content
3
sap.ui.core. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1293
sap.ui.table. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1296
sap.uxap. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1297
Glossary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1321
4
OpenUI5: UI Development Toolkit for HTML5
Content
OpenUI5: UI Development Toolkit for HTML5
Create apps with rich user interfaces for modern web business applications, responsive across browsers and
devices, based on HTML5. (Documentation version 1.52.9)
OpenUI5 offers powerful development concepts:
● One consistent user experience for your apps
● Responsive across browsers and devices - smartphones, tablets, desktops
● Built-in extensibility concepts at code and application level
● Data binding types and Model-View-Controller (MVC)
● Feature-rich UI controls for handling complex UI patterns and predefined layouts for typical use cases.
UI controls automatically adapt themselves to the capabilities of each device.
● Full translation support
● Keyboard interaction support and accessibility features
And many more....
Tip
Looking for the Demo Kit for a specific OpenUI5 version?
Check at https://openui5.hana.ondemand.com/versionoverview.html which versions are available. You can view
the version-specific Demo Kit by adding the version number to the URL, e.g. https://
openui5.hana.ondemand.com/1.38.8/
For more information, see Versioning of OpenUI5 [page 30].
What's New in OpenUI5
The following sections contain an overview of what's new in each version of OpenUI5.
Check the latest videos in the SAPUI5 playlist in the SAP Technology YouTube channel and the OpenUI5 YouTube
channel.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
5
What's New in OpenUI5 1.52
With this release the UI development toolkit for HTML5 (OpenUI5) is upgraded from version 1.50 to 1.52.
Improved Features
OData V2 Model
You can now optimize dependent bindings in OData V2 models. The OData V2 model now supports a
preliminaryContext flag. When set to true, the OData model can bundle the OData calls for dependent
bindings into fewer $batch requests. Two bindings are considered dependent if one cannot be resolved without
the other being resolved first. For example, a relative binding cannot be resolved without a resolved absolute
binding.
OpenUI5 OData V4 Model
The new version of the OpenUI5 The OData V4 model has the following features:
● Handling of Edm.Stream in read-only mode
● Possibility to declare several batch groups as $auto or $direct
● Enhancements to the adapter to use the V4 model with an OData V2 service: handling of simple $filter
expressions
Caution
Incompatibility Due to a Bug Fix
If you call the sap.ui.model.odata.v4.Context#getObject() or the
sap.ui.model.odata.v4.Context#requestObject() methods without a parameter, the expected and documented
behavior is that the same result is returned as if the parameter sPath="" had been specified. However, due to a
bug, the return value wraps the expected output and can only be accessed using .value[0], for example
oContext.getObject().value[0].
If you have used this workaround, your application will break as of OpenUI5 version 1.44.6.
Solution: If your application needs to run with both the fixed and unfixed versions of OpenUI5, specify the
sPath="" parameter, for the sPath parameter. In both cases, you must not use the .value[0]workaround
any more.
Restriction
Due to the limited feature scope of this version of the OpenUI5 OData V4 model, check that all required features
are in place before developing applications. Check the detailed documentation of the features, as certain parts
of a feature may be missing. While we aim to be compatible with existing controls, some controls might not
work due to small incompatibilities compared to sap.ui.model.odata.(v2.)ODataModel, or due to
missing features in the model (such as tree binding). This also applies to controls such as TreeTable and
AnalyticalTable, which are not supported together with the OpenUI5 OData V4 model. The interface for
applications has been changed for easier and more efficient use of the model. For a summary of these changes,
see Changes Compared to OData V2 Model [page 635].
6
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
For more information, see OData V4 Model [page 594], the API Reference, and the sample in the Demo Kit.
Support Assistant
Support Assistant has been enhanced with the following features:
● Additional rule property Async
The default value of this property is false. If you set it to true, a resolve function is passed as a parameter of
the check function to allow you to resolve the asynchronous operation. Call fnResolve to indicate that the
asynchronous check function has finished. The asynchronous function waits 10 seconds before it times out.
● New options to check the location from which Support Assistant has been loaded:
○ When you click the Settings button in the Support Assistant toolbar, you can see the URL at the bottom of
the dialog box. A Copy button next to the URL allows you to copy the location to the clipboard.
○ In the Technical Information section of the report.
● Displaying issues and their severity
After an analysis run, you can see how many issues of each severity have been reported for each library. This
information is also available for the individual rules. In the Details section for each rule, you can see a severity
icon for each issue generated by that rule.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
7
Improved Controls
● sap.f.DynamicPage:
○ Additional visual indicators (arrow buttons) are now available. They are positioned either below the header
(when the header is expanded) or below the title (when the header is collapsed). The expanded/collapsed
state of the header can be toggled by clicking on the title or the arrow buttons. Both areas get a darker
color when hovering over them. For more information, see the sample.
○ A new area, designated for breadcrumb navigation, has been introduced at the top-left area of the title of
the page. It is enabled with the new breadcrumbs aggregation (type sap.m.IBreadcrumbs) that
belongs to sap.f.DynamicPageTitle. For more information, see the API Reference and the sample.
○ The sap.f.DynamicPageTitle has a new navigationActions aggregation, which is meant for
buttons with navigation semantics, such as Close, Full screen, and Exit full screen. For more information,
see the API Reference and the sample.
○ The sap.f.DynamicPageTitle has two new aggregations: expandedHeading and snappedHeading.
They enable the app to display different content in the heading for the expanded and collapsed states of
the header. The previous heading aggregation is not deprecated and works as a replacement for the new
ones. For more information, see the API Reference.
● sap.f.semantic.SemanticPage:
○ The control has a new titleContent aggregation and titlePrimaryArea property. The aggregation
allows you as an app developer to place content in the middle of the title area, displayed in both expanded
and collapsed (snapped) states of the header. The property determines which of the title areas (Begin,
Middle) is primary (shrinking at a lower rate, remaining visible as long as it can). For more information,
see the API Reference and the sample.
○ The new navigationActions aggregation that belongs to the sap.f.DynamicPageTitle is now
utilized when using the closeAction, fullScreenAction, and exitFullScreenAction aggregations
of the SemanticPage. The content added in the aggregations is automatically positioned in the title in an
ordered way. For more information, see the API Reference.
● sap.m.CheckBox: A new useEntireWidth Boolean property determines whether the value set for the
width property applies to the whole control or to the control label only. For more information, see the API
Reference.
● sap.m.DateRangeSelection: The binding is improved and the control now supports date intervals. For
more information, see the API Reference.
● sap.m.FeedListItem provides new actions for defining additional features. You can find the features in an
sap.m.ActionSheet when you press the Action button. For more information, see the API Reference.
● sap.m.MessagePage: A new iconAlt property has been introduced. It enables the setting of an alt
attribute for the icon displayed on the sap.m.MessagePage. For more information, see the API Reference.
● sap.m.MessageView has new default behavior and a new property. These changes aim to save space on the
screen and hide unnecessary controls.
○ If only one item is available, the details page for the first item is used as the initial page after opening a
MessagePopover or rendering a MessageView.
○ If there are messages of only one type, the filtering options in the header will be hidden.
○ A new showDetailsPageHeader Boolean property for hiding the details page header has been
introduced. When set to false, no header will be displayed in the details section of the message.
For more information, see the Sample.
● sap.m.OverflowToolbar: Until now, controls needed to be whitelisted in a central helper class to be allowed
to move to the overflow menu. With the addition of the new sap.m.IOverflowToolbarContent interface,
8
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
each control can now provide this data on its own, without needing to be whitelisted. For more information, see
the API Reference.
● sap.m.PlanningCalendar: There is a new showWeekNumbers property that enables the display of the
calendar week numbers. They are available for the Days, 1 Week, and 1 Month views of the
sap.m.PlanningCalendar. For more information, see the API Reference and the sample.
● sap.m.QuickView has new default behavior. When the QuickView is only one page, and when no header is
set, the header is no longer rendered. For more information, see the Sample.
● sap.m.RatingIndicator has a new editable property. When set to true, the control can be edited and
you can rate using the icons. When set to false, the rating indicator is presented in a non-interactive mode
and can be used, for example, to visualize an aggregated rating score.
● sap.m.SegmentedButton: After the buttons aggregation was deprecated in version 1.28, both the select
event and selectedButton association are no longer useful for the recommended items aggregation. For
this reason, the select event and selectedButton association are deprecated as of version 1.52 and are
replaced by new ones - the selectionChange event and the selectedItem association. For more
information, see the API Reference and the sample.
● sap.m.Table and sap.ui.table.Table: Alternate row styling is now supported with the new
alternateRowColors property. For more information, see the API Reference and the Sample for
sap.m.Table and the API Reference and the Sample for sap.ui.table.Table.
● sap.m.Table: A new popinLayout property has been added that defines the layout for pop-in rows. For
more information, see the API Reference and the Sample.
● sap.m.Text has a new renderWhitespace property. It specifies the rendering of whitespace characters
(“ “) and tabs (\t) inside the control. The default value is false. If set to true, the browser preserves
whitespace characters and tabs. Line breaks (\r\n, \n\r, \r, \n) are always visualized unless the wrapping
property is set to false. For more information, see the API Reference and the Samples.
● sap.m.Title has a new wrapping property. It determines if the title will be wrapped. The default value is
false. If set to true, the entire title is wrapped. You should only activate the wrapping if the surrounding
container allows flexible heights. For more information, see the API Reference and the Samples.
● sap.tnt.NavigationList and sap.tnt.SideNavigation have a new selectedItem association. It
allows you to specify which NavigationListItem will be selected when you load the page and enables you
to change the selected Menu item using the code. For more information, see the API Reference and the
Samples.
● sap.tnt.NavigationListItem has a new visible property. It allows you to choose if a
NavigationListItem will be rendered in the NavigationList or not. By default it is set to true. For more
information, see the API Reference and the Samples.
● sap.ui.table.Table: You can now use drag and drop for table rows with aggregation dragDropConfig.
For more information, see the API Reference for the dragDropConfig aggregation, the API Reference for
sap.ui.core.dnd, the Sample for TreeTable and the Sample for Table.
● sap.ui.unified.FileUploader:
○ A new valueStateText property has been introduced to enable the display of custom messages when a
valueState is set. For more information, see the API Reference.
○ The new xhrSettings aggregation allows app developers to specify settings for the internally used
XMLHttpRequest object when uploading files with sendXHR=true. Currently the only supported setting
is withCredentials. For more information, see the API Reference.
○ A new sap.ui.unified.IProcessableBlobs interface has been implemented and
sap.ui.unified.FileUploader has been updated to use it. It facilitates the implementation of
custom controls that extend the sap.ui.unified.FileUploader, for example, to modify the files
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
9
before they get uploaded. To enable the feature, you need to set the bPreProcessFiles parameter of
the upload method to true. As a result, the method becomes asynchronous. For more information, see
the API Reference.
● sap.uxap.ObjectPageLayout now contains a new type of header. The new header is flexible and dynamic
as opposed to the classic header that has a specific and predefined layout. To implement the new dynamic
header, the app should provide an instance of the new sap.uxap.ObjectPageDynamicHeaderTitle
control as the value of the headerTitle aggregation.
With the new dynamic header the following features are available:
○ General-purpose aggregations instead of semantic properties that allow you to build a custom header
layout
○ An arrow button displayed below the header content and visual indicators when hovering over the title and
the arrow button
○ The title and the arrow button can be clicked to expand or collapse the header
○ A pin button allowing the header to remain expanded when scrolling the page or clicking/tapping the title
○ sap.m.OverflowToolbar is used internally to implement the actions aggregation, enabling its
features, such as the priority and grouping of the actions.
For more information, see Object Page Headers [page 1301], the API Reference, and the sample.
Demo Kit Improvements
A big thank you to everyone who sent us valuable feedback from many different channels. We are working hard to
implement your suggestions.
With this version, we have improved the performance of the Demo Kit app, enhanced link handling and scrolling,
improved quality, solved many issues, and added several new features:
General
● Compact display mode is always used so that white space is utilized better for all devices.
● Link processing is improved and links can be opened in a new browser tab.
● Performance is improved for initial loading, deep linking, and the API Reference.
● Busy indicators are added.
● The error pages are improved and new ones are added.
● Global search: Clicking the magnifier icon puts the focus on the search field.
● Visualization of search results on small screens is improved.
Documentation
● The root level node of the documentation tree is removed to improve usability.
● The documentation can be downloaded in PDF format from the Download button on the landing page of the
Documentation section.
API Reference
White spaces are decreased, contrast between the background and the tables is increased, and the structure of
the content is improved.
● The URL hash (#) is updated when selecting sections and subsections and while scrolling through the page
sections.
10
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
● Thrown exceptions are displayed.
● The following information is displayed in the header:
○ Cross links to Documentation are added.
○ Abstract classes are displayed in front of the class name.
○ Known direct subclasses and known direct implementations are displayed. If there is more than one
instance, they are visualized in a popover.
○ Class visibility information is added.
○ Library information for a certain namespace is added.
Samples
● The cross-links between Samples and API Reference are more prominent.
● About sections with descriptions are added for the tutorial samples.
Tools
We added a new Tools section in the main navigation after the Demo Apps section. It contains documentation and
quick navigation to different OpenUI5 tools, such as SAP Web IDE, UI5 Inspector, the icon explorer, the theme
parameter toolbox, UI theme designer, and Build.
Note
The icon explorer and the theme parameter toolbox were moved from Demo Apps to the Tools section.
Documentation and Templates
Check out the following new and updated documentation topics:
● Test Automation [page 899] that describes the setup for automated testing with Karma
● Continuous Integration: Ensure Code Quality [page 1010]
● Performance: Speed Up Your App [page 1047]
Our tutorials and templates have been updated according to the latest practices:
Change
IDs added:
●
Component ID in all HTML pages that instantiate a UI
component manually
new ComponentContainer({
height : "100%",
name : "sap.ui.demo.worklist",
settings : {
id : "worklist"
}
})
●
Why?
Unique IDs are necessary for many tools and testing scenar­
ios. If there is no ID specified for an object, it gets a generated
ID.
For components, this can be confusing in scenarios in which
more than one component is being used.
For target IDs, this leads to unpredictable view IDs as the views
are generated by the router in the sequence in which they are
called.
IDs for targets in the routing configuration in the
manifest.json descriptor file
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
11
Change
Why?
Asynchronous processing added:
Asynchronous loading of views (or any resources) can be
●
In the routing configuration in the manifest.json de­
scriptor files
●
Loading of the rootView in the manifest.json de­
scriptor files
●
Loading of the rootView in the Component.js files
rootView : {
faster in modern browsers. The browser can load resources in
parallel without freezing the UI. So this may improve the per­
formance and user experience.
Note
There are rules in the support assistant that check for
asynchronous loading.
viewName:
"sap.m.sample.ActionListItem.List",
type: "XML",
async: true,
id: "app"
}
getView().byId replaced by byId on all controller files
It is shorter, hence easier to read and type. It even makes writ­
ing some unit tests easier because developers do not need to
“stub” the getView() method.
Namespace of the Walkthrough tutorial renamed from
Long namespaces can make resource paths pretty long, so
walkthrough to wt
this is easier to understand.
What's New in OpenUI5 1.50
With this release the UI development toolkit for HTML5 (OpenUI5) is upgraded from version 1.48 to 1.50.
New Demo Kit (already available as of version 1.48.5)
The Demo Kit app has a new modern design that is intuitive and can be used on both desktop and mobile devices.
The global search has been improved and the results are now displayed in categories. They also include results
from the Samples section, which was not possible with the old Demo Kit since the Explored app was a decoupled
app.
The new Demo Kit contains the following sections:
●
Landing page with getting started information
●
Documentation with detailed information
●
API Reference with JavaScript documentation about the
framework and the UI controls, including details for the
corresponding properties, aggregations, associations,
events, and methods
12
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
●
Samples, showcasing almost all controls with ability to
download the sample code
●
Demo Apps, showcasing real-life scenarios that can
easily be downloaded
New Features
● The Unicode Common Locale Data Repository (CLDR) has been updated to version 31.
● The correct plural category for a given number is now handled by the locale-specific plural rules offered by
CLDR. Different languages use different plural forms, some languages have only singular and plural, others
require additional forms, for example, dual (two), paucal (few), or many.
● Date interval types are introduced to format two date-related properties from a model for displaying in the UI.
Additionally they are used to parse and validate the values in UI controls before they are saved back to the
model. The new interval types are:
○ sap.ui.model.type.DateInterval - a date interval (without time)
○ sap.ui.model.type.DateTimeInterval - a date interval with the exact point of time
○ sap.ui.model.type.TimeInterval - a time interval (without date)
For more information, see sap.ui.model.type.DateTimeInterval [page 701] and the API Reference.
● The configuration option animationMode replaces animation, which is now deprecated. The new option
supports several states (full, basic, minimal, none), which allow controls to extend support for animations
in a more granular way instead of a binary on/off state. For more information, see Configuration Options and
URL Parameters [page 485], Implementing Animation Modes [page 1166], and the API Reference.
New Controls
● sap.m.PlanningCalendarLegend: Enables two types of items to be displayed in the
sap.m.PlanningCalendar as a legend - types of days (for example, special dates and holidays) and
appointments. For more information, see the API Reference.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
13
Improved Features
OpenUI5 OData V4 Model
The new version of the OpenUI5 OData V4 model introduces an adapter that allows you to use an OData V2 service
together with the OData V4 model in read scenarios. The adapter offers the following features:
● Metadata is converted, including some V2 annotations.
● Data in the response is converted.
● Literals in the request URI are converted, except for Edm.DateTime, Edm.DateTimeOffset, Edm.Time, and
Edm.Binary.
● $select, $expand, and $orderby are handled. Cases that are not supported by OData V2, like $orderby in
$expand, lead to an error.
● All unsupported query options lead to an error.
Caution
Incompatibility Due to a Bug Fix
The following bug has been reported: If you call the sap.ui.model.odata.v4.Context#getObject() or the
sap.ui.model.odata.v4.Context#requestObject() methods without a parameter, the expected and documented
behavior is that the same result is returned as if the parameter sPath="" had been specified. However, due to
the bug, the return value wraps the expected output that can then only be accessed via .value[0], for
example oContext.getObject().value[0].
If you have used this workaround, your application will break starting with OpenUI5 version 1.44.6.
Solution: If your application needs to run with both the fixed and unfixed versions of OpenUI5, specify the
sPath="" parameter, for the sPath parameter. In both cases, you must not use the workaround
with .value[0] any longer.
14
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Restriction
Due to the limited feature scope of this version of the OpenUI5 OData V4 model, check that all required features
are in place before developing applications. Check the detailed documentation of the features, as certain parts
of a feature may be missing. While we aim to be compatible with existing controls, some controls might not
work due to small incompatibilities compared to sap.ui.model.odata.(v2.)ODataModel, or due to
missing features in the model (such as tree binding). This also applies to controls such as TreeTable and
AnalyticalTable, which are not supported together with the OpenUI5 OData V4 model. The interface for
applications has been changed for easier and more efficient use of the model. For a summary of these changes,
see Changes Compared to OData V2 Model [page 635].
For more information, see OData V4 Model [page 594], the API Reference, and the sample in the Demo Kit.
Support Assistant: OPA Test Sample Added
With the roll-out of Support Assistant in version 1.48, we introduced the possibility to use the tool in OPA tests to
check if there are issues in the different states of an application. This is possible by enabling the available OPA
extension.
As of this version, there is now a sample of the OPA integration in the Demo Kit. It demonstrates how you can
extend existing OPA tests by making calls to the assertions in the Support Assistant extension. These assertions
may have different severity, execution scope and subset of rules which are taken into consideration. The sample
also shows how to execute rule checks and how to get reports.
As of this version, there is now a sample of the OPA integration in the Demo Kit. For more information, see
Integrating the Rules in OPA Tests [page 955] and the Sample.
Improved Controls
● sap.f.DynamicPage has the following new features:
○ You can now define the priority of the DynamicPageTitle areas with the use of the new primaryArea
property. The primary area shrinks at a slower rate, remaining visible as long as possible.
○ With the new content aggregation of the DynamicPageTitle, you can add content in the middle area of
the title. This content is displayed both in the expanded and collapsed states of the DynamicPageHeader.
For more information, see the API Reference.
● sap.f.semantic.SemanticPage: To align with the latest SAP Fiori design guidelines, the following changes
were implemented:
○ The position of the draft indicator is changed to be the first one before the finalizing actions in the footer
toolbar of the page.
○ A new Edit button was added as the main action and the order of the actions changed to Edit, Delete,
Copy, and Add.
For more information, see Semantic Page (sap.f) [page 1200], the API Reference, and the sample.
● sap.m.Label has the following new properties:
○ wrapping: Determines whether text within a label is wrapped. The default value is false. If set to false,
the label text only uses one line and any exceeding text is truncated and replaced with an ellipsis. When
you use the Label within a sap.m.Form the label text is automatically displayed as wrapped.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
15
○ displayOnly: Determines whether the label is in displayOnly mode. Controls in this mode are noninteractive, non-focusable, cannot be edited, and do not form part of the tab chain. The displayOnly
property is used in Form controls when they are in preview mode.
For more information, see the API Reference and the Samples.
● sap.m.MessageStrip now supports a limited set of formatting tags for the text. The available tags are <a>,
<em>, <strong>, and <u>. To enable the additional formatting tags, you have to set the
enableFormattedText property to true. For more information, see the API Reference and the Samples.
● sap.m.Panel has a new parameter for the expand event that identifies whether the user or the application is
expanding or collapsing the Panel control. The parameter is called triggeredByInteraction and is true
when the panel is expanded as a result of a user action. For more information, see the API Reference and the
Samples.
● sap.m.PlanningCalendar:
○ You can now directly navigate to a date with fewer clicks.
○ With the use of the new builtInViews property the app developer can now define which of the built-in
views are displayed. For more information, see the API Reference and the sample.
○ To save space, the days are now displayed on the same line as the dates. If you want to display the day
names on a separate line, set the showDayNamesLine property to true. For more information, see the
API Reference and the sample.
● sap.m.ProgressIndicator: You can now set the control in a display-only state using the new
displayOnly property. When set to true, the control has different visualization and is not active, not
editable, and cannot be focused. For more information, see the API Reference and the Samples.
● sap.m.RatingIndicator: A new state can be set using the displayOnly property. It enables visually
distinguishable rendering of the RatingIndicator (gray color), denoting it as non-interactive in forms. All
controls in this mode are also non-focusable and not part of the tab chain. For more information, see the API
Reference and the Sample.
● sap.m.semantic.SemanticPage. You can now set the background color of the page using the new
backgroundDesign property. For more information, see Semantic Page (sap.m) [page 1203] and the API
Reference.
● sap.m.UploadCollection: UploadCollectionItem has been extended to display folders in the
UploadCollection control. When you click the file name or item thumbnail, you can perform custom actions
by adding an event handler to the press event. With the deletePress event, you can control the deletion of
an item. For more information, see the API Reference and the sample.
16
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
● sap.m.Tree: The toggleOpenState event has been added. For more information, see the API Reference
and the sample.
● sap.ui.unified.Calendar: The days of the previous/next month are no longer visible whenever the
sap.ui.unified.Calendar displays multiple months. For more information, see the API Reference and the
sample.
● sap.ui.layout.form.Form and sap.ui.layout.form.SimpleForm: The samples, including the
descriptions, have been simplified and are now more consistent. For more information, see the Form and the
SimpleForm samples.
Read Me First
Before you start using OpenUI5, please read the important information in the section. Here you read everything
you need to know about supported library combinations, the supported browsers and platforms, and so on.
● Compatibility Rules [page 18]
● Browser and Platform Support [page 21]
● Supported Library Combinations [page 27]
● Supported Combinations of Themes and Libraries [page 28]
● SAPUI5 vs. OpenUI5 [page 37]
● Deprecated Themes and Libraries [page 34]
● Upgrading [page 32]
● Versioning of OpenUI5 [page 30]
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
17
Compatibility Rules
The following sections describe what SAP can change in major, minor, and patch releases. Always consider these
rules when developing apps, features, or controls with or for OpenUI5.
Caution
As an app developer, never do the following:
● Never manipulate HTML/CSS via JavaScript (domRef.className = "someCSSClass";) or directly via
CSS, for example. Always follow our recommendations under CSS Styling Issues [page 1060].
● Never use or override "private" functions that are not part of the API Reference. Private functions are
typically (but not always) prefixed with a preceding "_". Always double-check the API Reference, private
functions are not listed there.
API Evolution
Unless otherwise mentioned, the word "API" in this section refers to "public API", meaning functions, classes,
namespaces, controls along with their declared properties, aggregations, and so on. The sole definition of the
public API is the API Reference, which is included in the OpenUI5 Demo Kit. Features that are not mentioned there
are not part of the API.
The following rules apply for introducing new APIs or making incompatible changes to existing APIs:
Major release (x.yy.zz): A new major version can introduce new APIs or make incompatible changes to existing
APIs.
Minor release (x.yy.zz): A new minor version can introduce new APIs but must not contain incompatible changes
to any APIs.
Patch release (x.yy.zz): A new patch version only contains fixes to the existing implementation, but does not
usually contain new features or incompatible API changes.
Note
Exceptions to these rules are possible, but only in very urgent cases such as security issues. Such exceptions
are documented in the Change Log.
Compatible Changes
The following changes to existing APIs are compatible and can be done anytime:
● Adding new libraries, controls, classes, properties, functions, or namespaces
● Generalizing properties, that is moving properties up in the inheritance hierarchy
● Adding new values to enumeration types; this means that when dealing with enum properties, always be
prepared to accept new values, for example, by implementing a "default" or "otherwise" path when
reacting on enum values.
18
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Incompatible Changes
The following is not part of the public API and may change in patch and minor releases:
● Open source libraries (see Third-Party Open Source Libraries [page 20])
● Log messages
The following changes to existing APIs are incompatible, but can be done in a new major release:
● Renaming an API (library, namespace, function, property, control, events, and so on)
● Removing support for parameters
● Removing support for configuration entries
● Reducing the visibility of an API; this does not break JavaScript applications, but changes the contract
● Removing or reordering parameters in an API signature
● Reducing the accepted value range, for example, parameter of a function
● Broadening the value range of a return value (or property). Exception: enumerations
● Moving JavaScript artifacts (namespaces, functions, classes) between modules
● Replacing asserts with precondition checks
● Moving properties (and so on) down in the inheritance hierarchy
● Changing the name of enum values
● Changing defaults (properties, function parameters)
● Renaming or removing files
Inheritance
Inheriting from OpenUI5 objects (e.g. by calling sap.ui.extend on an existing control to add custom
functionality) may endanger the updatability of your code.
When overriding an OpenUI5 lifecycle method (such as init, exit, onBeforeRendering, and
onAfterRendering), you must make sure that the super class implementation is called, for example like this:
MyClass.prototype.onAfterRendering = function() {
SuperClass.prototype.onAfterRendering.apply(this);
// do your additional stuff AFTER calling super class
}
SAP might add, remove, or change the internal implementation of the parent class at any time. Especially, you
should not rely on the following functionality:
● Internal structures and methods that are not part of the public API
● Any internal logic and behavior of the object that is not reflected in the public API
● The parent hierarchy of objects especially for composites where the API parent differs from the real parent
(e.g. parent object > internal object > child object). For more information, see API Reference:
sap.ui.base.ManagedObject.
● All rendering functionality of a control, including the HTML structure and CSS classes
● Naming collisions with OpenUI5 structures and methods. OpenUI5 might introduce new API or internal
structures at a later point in time that collide with your implementation. To avoid collisions, a custom prefix
may be applied. Don't use namespaces starting with sap.m.* or sap.ui.* in your app.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
19
We recommend that you test inherited classes very carefully after updating OpenUI5 to make sure that the
extended functionality is still working as expected.
Deprecation
If possible and appropriate, we mark old artifacts as deprecated and create new artifacts, instead of making
incompatible changes. A deprecation comment in the corresponding API documentation, and perhaps also a log
entry in the implementation, explain why and when an artifact has been deprecated and include tips on how to
achieve the same results without using deprecated functionality.
Experimental API
Some features or controls delivered with the current OpenUI5 version are flagged as "experimental". These
experimental features and controls are not part of the released scope of the delivered OpenUI5 version. Do not use
experimental features or controls in a productive environment, or with data that has not been sufficiently backed
up.
Experimental features and controls can be changed or deleted at any time without notice, and without a formal
deprecation process. They may also be incompatible to changes provided in an upgrade.
Third-Party Open Source Libraries
OpenUI5 contains and uses several third-party open source libraries, such as jQuery. These libraries can also be
used by applications and/or custom control libraries, but the OpenUI5 compatibility rules described in this
document do not apply to these third-party libraries.
If you want to use the third-party open source libraries included in OpenUI5, note the following restrictions:
● SAP decides which versions and modules of the used libraries are provided.
● SAP can upgrade to a higher version of the used libraries even within a patch release.
● For important reasons such as security, OpenUI5 can stop providing a library at any time.
● The third-party libraries are provided "as is". Extensions, adaptations, and support are not performed or
provided by SAP.
Note
You are only allowed to use closed source libraries for the OpenUI5 controls for which they are intended.
For a list of the third-party open source software used in OpenUI5, see the section under OpenUI5 Subcomponents
on the License page of the Demo Kit.
20
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Related Information
Versioning of OpenUI5 [page 30]
API Reference
Browser and Platform Support
Browser and platform support for the OpenUI5 libraries on iOS, Android, macOS, and Windows platforms.
As OpenUI5 is based on CSS3, HTML5, and the ECMAScript 5 (ES5) JavaScript API, only browsers with HTML5
capabilities are supported. In general, only major versions that are also supported by the respective platform can
be supported by the OpenUI5 framework.
Restriction
We currently do not guarantee that newer ECMAScript standards, such as ES6/ES2015, work with OpenUI5.
Depending on the platform your OpenUI5 apps run on, different browsers in different versions are supported. If you
know which platform and which browsers are used by your users, you can decide on which libraries to use for your
app.
Overview of Supported Browsers, Platforms, and Reference Devices
The following tables give a general overview of the browsers, platforms, and reference devices supported by the
main OpenUI5 libraries.
Browser and Platform Support Matrix
Note
OpenUI5 plans to end the support for Microsoft Internet Explorer 11 end of Q1/2021. The planned OpenUI5
version 1.60 with planned release date Q1/2019 and planned end of maintenance end of Q1/2021 will be the last
OpenUI5 version that supports Internet Explorer 11.
Platform
Windows
Device
Category
Desktop
Touch3
Platform
Version
Safari
Web View
Internet
Explorer
Microsoft
Edge
Windows
7, 8.1
-
-
Version 112
-
Windows
10
-
Latest ver­
sion
Windows
8.1
-
-
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Latest
Current
Branch for
Business
Version 112
-
Google
Chrome
Mozilla
Firefox
SAP Fiori
Client
Latest ver­ Latest ver­
sion
sion and
Extended
Latest ver­
Support
sion
Release
1
(ESR)
-
-
Up to ver­
sion 1.5.3
21
Platform
Device
Category
Platform
Version
Internet
Explorer
Microsoft
Edge
Safari
Web View
Windows
10
-
Latest ver­
sion
-
Latest ver­
sion
-
Latest ver­
sion
Latest
Current
Branch for
Business
Google
Chrome
Mozilla
Firefox
SAP Fiori
Client
Latest ver­ Latest ver­ Latest ver­
sion
sion
sion and
Extended
Support
Release
(ESR)1
Windows
Phone
Phone3
Windows
10 Mobile
macOS
Desktop
Latest 2
versions
Latest 2
versions
-
-
-
iOS
Phone and Latest 2
Tablet3
versions
Latest 2
versions
Latest ver­
sion
-
-
-
-
Latest ver­
sion
Android
Phone and As of ver­
Tablet3
sion 5
-
-
-
Latest ver­
sion
-
Latest ver­
sion
-
-
-
Latest ver­
sion
Latest ver­ sion 3
-
1) In regards to handling touch events, there are some issues with Windows 8. For more information, see Windows
8 Support - Known Issues [page 1154].
2) Internet Explorer 11 requires add-ons XML DOM Document and XML DOM Document 3.0 to be activated for XML
parsing support.
3) Not supported for sap.ui.commons and sap.ui.ux3.
Supported Reference Devices
For mobile operating systems, support is restricted to specific reference devices.
When creating support incidents, make sure that the device you refer to belongs to the listed ones:
Note
Touch-enabled devices are not supported by the sap.ui.commons and sap.ui.ux3 libraries.
Device
Platform
End of Support Date
Apple iPhone 5 SE
31 March 2019
Apple iPhone 6
19 September 2017
Apple iPhone 6s
25 September 2018
exceeding 3 years from vendor release
Apple iPhone 7
7 September 2019
date. OpenUI5 supports Apple mobile
Apple iPad Air 2
22 October 2017
Apple iPad Mini 3
24 October 2017
Apple iPad Pro
9 September 2018
Samsung S6
10 April 2018
Samsung S7
10 March 2019
mented in matters of operating system
Samsung S8
21 April 2020
variants and hardware diversity.
Samsung Galaxy Tab S2 (8.0)
3 September 2018
iOS
SAP always supports the 2 latest re­
leases of the iOS operating system, not
devices 3 years from the vendor device
release date, except defined otherwise.
Android
Android OS based devices are very frag­
22
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Platform
Device
End of Support Date
OpenUI5 supports the following Android
reference devices until 3 years from ven­
Samsung Galaxy Tab S3 (9.7)
26 February 2020
Microsoft Lumia 950
20 November 2018
dor device release date.
Windows Phone
OpenUI5 supports the following Win­
dows Phone reference devices until 3
years from vendor device release date.
Additional Information
● General
○ Internet Explorer 11 (IE11) provides specific document and enterprise modes for compatibility reasons.
OpenUI5 supports only the IE11 and IE10 document modes. For backward compatibility, IE11 allows to
enable a special enterprise mode that can simulate either an IE8 or IE7 within an IE11, which is NOT
supported for OpenUI5 apps. This functionality should be used only for critical apps that require an older
browser version to run. For more information, see "Fix web compatibility issues using document modes
and the Enterprise Mode site list" in the Microsoft Windows IT Center.
○ The PhantomJS browser is not supported. If you are using it for testing purposes, make sure that you use
version 2.x or higher. Otherwise you may get an error message, such as "TypeError: 'undefined' is
not a function (evaluating 'f.bind(null,undefined)')(line 146)".
○ sap.ui.core, sap.ui.layout, sap.ui.unified are basic libraries, supporting all platforms or
browsers that are supported by any of the other libraries.
● sap.m
○ For the maxLines property of the sap.m.Text control, multiline ellipsis handling is not supported for all
browsers and devices and is not supported at all for right-to-left text direction. For more information, see
Visual Degradations [page 23].
○ Certain new or more complex controls that do not yet offer mobile device support also do not support
Windows Phone at this time, such as: sap.m.FacetFilter and sap.m.MultiComboBox.
○ When using the redirect method of the sap.m.URLHelper control, note that some browsers fail to
open a new window or tab. This is the case for Windows Phone, for instance. As the redirect method is
simply a wrapper of window.open native JavaScript, the problem is that Internet Explorer 10 for Windows
Phone is unable to return a valid window object for window.open. In addition, popup blockers can also
prevent this function from working properly.
Visual Degradations
Depending on the combination of device and browser, visual degradations may occur in the sap.m library.
The following sections give an overview of the known degradations.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
23
sap.m.Text - maxLines (property), sap.m.Text - text (property),
sap.m.ObjectListItem - title (property), sap.m.ObjectHeader - title (property)
The visual aid for indicating multiline overflow is an ellipsis at the end of a line. This ellipsis is displayed if the text
string exceeds the maximum number of lines displayed on screen. Depending on the line-clamping support offered
by your browser, this visual aid may not be displayed at all, meaning the text is simply truncated without any visual
indication that it is incomplete. The table below outlines which browsers fail to support the multiline ellipsis
handling of the maxLines property, and also shows examples of each visual degradation along with what the
display should look like in each case:
Table 1: maxLines Visual Degradations
What it Should Look Like
Visual Degradation
Google Chrome
Mozilla Firefox
Internet Explorer 10
placeholder Property in sap.m.Input and sap.m.TextArea
As there is no W3C specification for how to use the placeholder property, browser handling for this property varies
greatly. Some browsers use a native placeholder property, but for browsers that do not support this, SAP
implements its own placeholder version.
The following overview outlines which browsers use which version, and which limitations or degradations apply in
each case for the sap.m.Input control and sap.m.TextArea control.
24
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Table 2: placeholder Property in sap.m.Input
Browser
Situation
Google Chrome
Google Chrome supports the native placeholder property and
displays the ellipsis correctly, indicating that the placeholder
text stretches beyond the field that is currently visible
Internet Explorer Version 11
This version supports the native placeholder property but
does not display the ellipsis, instead it simply truncates the
placeholder text string
Note
If you focus in the field, the placeholder disappears from
view. If you leave the focus without typing anything, the pla­
ceholder is then displayed again.
Mozilla Firefox
Mozilla Firefox currently supports the native placeholder prop­
erty and displays the ellipsis correctly, indicating that the pla­
ceholder text stretches beyond the field that is currently visi­
ble
Whereas sap.m.Input contains just a single line placeholder, sap.m.TextArea is a multiline control, meaning it
brings with it different issues to the ones listed above. These issues are different depending on the browser and are
listed below.
Table 3: placeholder Property in sap.m.TextArea
Browser
Situation
Google Chrome
Google Chrome supports the native placeholder property and
displays multiple lines along with a scrollbar
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
25
Browser
Situation
Internet Explorer Version 11
This version does not use the native placeholder property, in­
stead it uses the placeholder version developed by SAP. This
browser displays multiple lines along with a scrollbar.
Note
Clicking the scrollbar sets focus from a technical perspec­
tive, meaning the placeholder text disappears and makes
scrolling impossible. Scrolling with the mouse wheel does
not set focus, and enables you to read the entire place­
holder text.
Mozilla Firefox
Mozilla Firefox supports the native placeholder property but
does not display a scrollbar or an ellipsis, instead it simply
truncates the placeholder text string
Issues Affecting sap.m.TextArea on iOS Devices
Note
There are several issues affecting iOS devices (as of version iOS 5), which are often mistakenly identified as
errors relating to sap.m.TextArea rendering. These are not visual degradations relating to sap.m. These
rendering errors are inherent to the iOS operating system as of version 5, and also occur even when OpenUI5 is
not used at all, as shown in the examples below.
26
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Problem
Description
When the text entered inside a text box is long and does not
fit into the fixed visible text area, the cursor then appears
outside of the visible text box, as shown in the graphic.
When choosing Select All to copy the contents of a text box
on an iOS device, the selection marked in blue continues out­
side the boundaries of the visible text area, as shown in the
graphic.
Supported Library Combinations
OpenUI5 provides a set of JavaScript and CSS libraries, which can be combined in an application using the
combinations that are supported.
There are two sets of possible library combinations, which are best described using the diagram below. Any of the
libraries listed on the lefthand side can be used with those listed in the middle, and any of the libraries listed on the
righthand side can be used with the ones listed in the middle. The libraries listed on the lefthand side cannot be
used in combination with the libraries listed on the right, and vice versa:
Note
Libraries that are not mentioned explicitly, belong to the lefthand side.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
27
Related Information
Deprecated Themes and Libraries [page 34]
Supported Combinations of Themes and Libraries
This chapter gives an overview of the possible combinations of themes and libraries for the OpenUI5 versions that
are still in maintenance.
Active Libraries
The following table shows which themes are available for the active OpenUI5 libraries. Even though the
sap_bluecrystal and the sap_hcb themes are now deprecated, they currently are still available, but will not be
maintained. We recommend that you migrate your existing apps to the supported themes.
28
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Table 4: Themes for Supported Libraries
1.38
1.44
1.46
1.48
n/a
sap_belize
sap_belize_plus
n/a
sap_belize_hcb
n/a
sap_belize_hcw
sap_bluecrystal
sap_hcb
= Supported,
= Not Supported
Deprecated Libraries
Should you decide to ignore the deprecation of libaries and continue, for example, using sap.ui.commons and
sap.ui.ux3, we recommend you use the sap_bluecrystal or sap_belize theme.
The sap_bluecrystal theme is also no longer supported, but offers full coverage of the sap.ui.commons and
the sap.ui.ux3 library. It is currently still shipped, but will be removed in one of the next versions.
The sap_belize theme offers an initial implementation for the sap.ui.commons and the sap.ui.ux3 library to
allow for a smoother transition, but it is not supported for this library and will not be maintained. We recommend
that you consider migrating your existing apps to actively developed libraries, such as sap.m, and use
sap_belize as the default theme going forward.
Table 5: Themes for the Deprecated Libraries sap.ui.commons and sap.ui.ux3
1.38
sap_belize
1.44
1.46
1.48
n/a
sap_belize_plus
sap_belize_hcb
n/a
n/a
sap_belize_hcw
sap_bluecrystal
sap_hcb
sap_goldreflecti
on
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
29
1.38
1.44
1.46
1.48
sap_platinum
sap_ux
= Supported,
= Not Supported,
= Deprecated,
= Removed
Related Information
Deprecated Themes and Libraries [page 34]
Versioning of OpenUI5
Versioning and maintenance strategy for OpenUI5.
OpenUI5 uses a 3-digit version identifier, for example 1.48.8. The digits have the following meaning:
● The first digit (1) specifies the release number (major version).
● The second digit (44) specifies the version number (minor version) .
● The third digit (7) specifies the patch number.
For minor OpenUI5 versions, we can deliver patches that contain fixes, but no new features. New features are only
delivered with the next minor version.
To view the documentation for a specific version, check at https://openui5.hana.ondemand.com/
versionoverview.html which versions are available. You can view the version-specific Demo Kit by adding the
version number to the URL, e.g. https://openui5.hana.ondemand.com/1.38.8/
To get an overview of the new features of each version, see What's New in OpenUI5 [page 5], to see the fixes
contained in each patch check the Change Log.
Maintenance Strategy
OpenUI5 provides innovations on a regular basis through "maintenance versions" and "innovation versions". As
long as an OpenUI5 version is still in maintenance, we provide patches with fixes.
An innovation version is only maintained until the next version of OpenUI5 is released.
Maintenance versions have an extended maintenance period in which we still provide patches even though a
higher version is already available. This is because the SAPUI5 version is included in a release of the SAP_UI
component.
For example, the following versions have an extended maintenance:
30
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
● 1.38 which is included in SAP_UI 7.50 and UI add-on 2.0
● 1.44 which is included in SAP_UI 7.51
● 1.52 which is included in SAP_UI 7.52
In the version overview at https://openui5.hana.ondemand.com/versionoverview.html, you can see which of the
OpenUI5 versions have an extended maintenance.
Figure 1: Overview of the maintenance strategy for OpenUI5 versions
Availability of Multiple Versions on the Akamai Content Delivery Network
All OpenUI5 resources are available on the content delivery network Akamai. There, you can also find multiple
OpenUI5 versions, and you can use them in your code as described in Variant for Bootstrapping from Content
Delivery Network [page 476].
Check the available versions with respective maintenance status at https://openui5.hana.ondemand.com/
versionoverview.html.
OpenUI5 Version (Core Version)
You can find which patch versions you use in you app in the technical information dialog ( Ctrl + Left Alt +
Shift + P ).
To access the OpenUI5 version (core version) at runtime, you use the following code:
var oConfig = sap.ui.getCore().getConfiguration();
var oVersion = oConfig.getVersion();
For more information, see SAPUI5 vs. OpenUI5 [page 37].
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
31
Upgrading
The following sections describe what you have to consider when upgrading to a new version of OpenUI5.
Upgrading from a Version Below 1.40
Older jQuery Versions Removed
As of this version, OpenUI5 only contains one version of jQuery (the current version is 2.2.3). This standard version
is always used when no other jQuery version is included in the bootstrap of an app. If you need a specific jQuery
version for your app, add and load it explicitly as described in noJQuery Variant for Bootstrapping [page 478].
Check the console for the related warning message if you are unsure which version you are using.
Upgrading from a Version Below 1.38
When upgrading to the current OpenUI5 version from a version below 1.38 (released in June 2016), check whether
the changes listed below influence your apps.
With this OpenUI5 version, jQuery has been upgraded to version 2.2.3.
This upgrade may impact your OpenUI5 apps. The following sections give an overview of our findings and how to
deal with them.
Note
If you use additional open-source libraries that depend on jQuery, check whether they need to be upgraded as
well.
jQuery.Event
Problem
jQuery removed some robustness checks in its event handling code. Without these checks, the jQuery.trigger
function must only be called with events that either have no originalEvent property or where the
originalEvent has all methods that window.Event implements (especially preventDefault,
stopPropagation and stopImmediatePropagation).
When a jQuery.Event is constructed with an object literal (properties) or when originalEvent is set to
some object after construction, this constraint is not fulfilled. Unfortunately, many OpenUI5 unit tests used this
approach to simulate mouse or key events.
32
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Solution
For each code that creates events, you have to apply the following fix:
The module QUnitUtils now rewrites the jQuery.Event constructor so that any given object literal is enriched
with the missing methods. Most OpenUI5 unit tests include the QUnitUtils module early, which then fixes the
issue.
Application code that needs to simulate an event, either should omit the originalEvent or use Event.create
to create a native event and only then create a jQuery.Event.
jQuery.fn.position
Problem
jQuery.fn.position now takes the scroll positions of the parent element into account. This change was
recoginzed as incompatible by the jQuery team and reverted with version 2.2.1.
Solution
Nothing, this is automatically fixed.
jQuery.now
Problem
jQuery.now is now set to Date.now for all browsers. But as the jQuery property represents a separate reference
to that function, it is not touched by code that modifies Date.now, especially not by Sinon fake timers. Therefore
Sinon fake timers don't work with jQuery 2.2 if Sinon is started after jQuery.
Solution
As a workaround, QUnitUtils redefines jQuery.now so that it delegates to the current Date.now. This will then
use any installed fake timer.
:visible selector
Problem
Somewhere between jQuery 1.11.1 and 2.2.0, the behavior of the :visible selector has changed. For empty inline
elements (for example, a span with no text), the selector now reports :visible = true whereas jQuery 1.1.1
reported it as hidden. There was only one functionality in the sap.ui.dt library where this change in behavior
caused problems.
Solution
Instead of using :visible, that functionality now uses its own implementation similar to jQuery 1.11.1.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
33
Sizzle attribute selector ([name=value])
Problem
In Microsoft Internet Explorer, the attribute selector no longer works when the attribute value is unquoted and
starts with a hash (#). This is the case when hash-name-references are searched for, like with the usemap attribute
of the IMG element.
Solution
Use quotes ("") for attribute values in those cases.
jQuery.isPlainObject
Problem
jQuery 2.2.0 simplified the implementation of jQuery.isPlainObject. As a side-effect, objects with a
constructor property with a non-function value (like a string value) caused a runtime error when
jQuery.isPlainObject was applied.
Solution
This issue is fixed with jQuery 2.2.2.
Descriptor for Applications, Components, and Libraries
If you want to add new attributes of a descriptor version higher than V2 (SAPUI5 1.30) to your existing
manifest.json file, see Migration Information for Upgrading the Descriptor File [page 550].
Deprecated Themes and Libraries
As OpenUI5 evolves over time, some of the UI controls are replaced by others, or their concepts abandoned
entirely. This chapter gives an overview on theme and library level of the most important deprecations. Individual
control deprecations and more information about the controls replacing them can be found in the API reference
within the Demo Kit.
Themes that are no longer supported
sap_hcb
The sap_hcb theme is deprecated as of version 1.48. It is replaced by the sap_belize_hcb theme.
34
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
sap_hcb is the High Contrast Black theme used for the already deprecated sap_goldreflection and
sap_bluecrystal themes. For sap_belize and sap_belize_plus there are two high contrast themes
available: sap_belize_hcb (High Contrast Black) and sap_belize_hcw (High Contrast White).
sap_bluecrystal
The sap_bluecrystal theme is only supported until version 1.38. It is replaced by sap_belize as the default
theme for OpenUI5 applications.
Custom themes based on sap_bluecrystal are no longer supported with 1.40 or higher.
sap_ux
The sap_ux theme is only supported until version 1.38. This was one of the very first OpenUI5 themes and is only
implemented by a small subset of the sap.ui.commons and sap.ui.ux3 controls, which are also
deprecated.This theme has been removed with OpenUI5 version 1.48.
sap_platinum
The sap_platinum theme is only supported until version 1.38. This was one of the very first OpenUI5 themes and
is only implemented by a small subset of the sap.ui.commons and sap.ui.ux3 controls, which are also
deprecated. This theme has been removed with OpenUI5 version 1.48.
sap_goldreflection
The sap_goldreflection theme is only supported until version 1.38. This was one of the first OpenUI5 themes
and is only implemented by the sap.ui.commons and sap.ui.ux3 controls, which are also deprecated. This
theme has been removed with OpenUI5 version 1.48.
Deprecated Libraries
sap.ui.commons
The sap.ui.commons library is deprecated as of version 1.38.
sap.ui.commons was available from the very beginning of OpenUI5. It contains a large number of basic UI
controls like buttons, input fields and dropdowns. With version 1.16, the sap.m library was introduced. It contains
semantically identical controls (button, input and select) that, at that time, were only supported on mobile
platforms. In later versions, sap.m was extended to support desktop platforms as well. For more information
about this, see Browser and Platform Support [page 21]. The sap.m controls were bigger in size to support mobile
displays that require a larger touch area. The “Compact Content Density” feature explained under Content
Densities [page 832] was then added to OpenUI5, allowing you to display a control in a more compact screen size.
Today, applications should be built one single time using sap.m (and other libraries) and their content density
switched at runtime depending on the environment. As such, the redundant library sap.ui.commons should no
longer be used.
Some of the controls in sap.ui.commons.layout have been replaced by the new dedicated layout library called
sap.ui.layout, which runs on the same platforms as sap.m.
Some of the old controls have been made available again through the non-deprecated sap.ui.unified library
(e.g. FileUploader, Menu), which runs on the same platforms as sap.m.
Some concepts such as Accordion and Row Repeater have been abandoned completely.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
35
sap.ui.ux3
The sap.ui.ux3 library is deprecated as of version 1.38.
This library contains more complex UI controls that were based on sap.ui.commons along the UX3 design
approach. The sap.m library - successor to sap.ui.commons - implements SAP’s new SAP Fiori design [http://
experience.sap.com/fiori-design/], which supersedes UX3. As such, the sap.ui.ux3 library is also deprecated.
Some of the UX3 concepts are reflected in SAP Fiori, some are abandoned, as outlined in the following table:
Concept
What's Happened?
Feeds
Replaced by sap.m (sap.m.Feed*).
Notification Bar
Replaced by sap.m (sap.m.MessagePopover and
sap.m.semantic*).
Thing Inspector
Indirectly replaced by a different design for displaying object
data.
Shell
Partially replaced by sap.ui.unified.Shell.
Data Set
Not part of SAP Fiori.
Exact
Not directly part of SAP Fiori. Use
sap.ui.comp.FilterBar or sap.m.IconTabBar for
filtering.
Quick Views
Concept abandoned as the concept of “hovering with the
mouse pointer over a control” does not exist on mobile devi­
ces.
For more information about the SAP Fiori design, see the SAP Fiori design guidelines.
Related Information
Index of Deprecated APIs
Supported Library Combinations [page 27]
Supported Combinations of Themes and Libraries [page 28]
36
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
SAPUI5 vs. OpenUI5
With SAPUI5 and OpenUI5 we provide two deliveries of our UI development toolkit. Both are very closely related,
but have their differences.
Licenses
The main difference is the license.
OpenUI5 is Open Source, free to use, released under the Apache 2.0 license. Since we also use many Open Source
libraries, we try to return the favor and also benefit from the experience and knowledge of developers all over the
world.
SAPUI5 is not a separate SAP product with a separate license. It's integrated, for example, in the following
products:
● SAP HANA
● SAP Cloud Platform
● SAP NetWeaver 7.4 or higher (included in the UI technologies (SAP_UI) component)
● User interface add-on for SAP NetWeaver Application Server 7.3x
Content
The easiest way to get an overview of which libraries are delivered is to have a look at the API Reference of the each
Demo Kit. You'll see that the list of libraries in SAPUI5 is much longer... which in no way means that OpenUI5
provides just a very limited scope!
Most importantly, the core containing all central functionality and the most commonly used control libraries is
identical in both deliveries. (For example, sap.m, sap.ui.layout, sap.ui.unified.)
So OpenUI5 also gives you all the important features needed to build feature-rich Web applications.
The additional libraries in SAPUI5 include more controls on top, like charts, and SAPUI5 also lets you use 'smart
controls', for example, which are controls that are automatically configured by OData annotations from the back
end. The exact feature range of SAPUI5 also depends on the platform you're using. For example, you can only use
the ABAP repository with SAP NetWeaver and not on SAP Cloud Platform.
Contributing to OpenUI5
OpenUI5 is Open Source, and is available on GitHub.
If you find a bug or have an idea for a new feature - just go ahead and propose a GitHub issue or a change. But
before you do so, please just read our guidelines first: Contributing to OpenUI5.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
37
Resources
For the OpenUI5 version, visit http://openui5.org/ where you can download the runtime and the Demo Kit (SDK) at
http://openui5.org/download.html.
For the SAPUI5 resources, check your platform installation.
Both resources are also available online via the content delivery network provider Akamai at https://
openui5.hana.ondemand.com/ and https://sapui5.hana.ondemand.com/.
Compatibility of OpenUI5 and SAPUI5
Technically, you can switch between OpenUI5 and SAPUI5 (providing you have the respective license), e.g. if you
want to use the SAPUI5-specific features.
Just check first which SAPUI5 version you need, because the version numbers of OpenUI5 and SAPUI5 might differ
on patch level (last number). You can find this information in the technical information dialog ( Ctrl + Alt +
Shift + P ).
If you're using the content delivery network, you can simply replace the bootstrapping reference to https://
openui5.hana.ondemand.com/<1.xx.yy>/ with a reference to https://sapui5.hana.ondemand.com/
<1.xx.zz>/. For more information, see Variant for Bootstrapping from Content Delivery Network [page 476].
For all other cases, replace the runtime. Since the technical names (of controls, libraries, etc.) and APIs are the
same in both OpenUI5 and SAPUI5, the code will still work and you can start enhancing it directly.
Get Started: Setup and Tutorials
Set up your development environment and go through our tutorials. They introduce you to all major development
paradigms of OpenUI5 using practical examples in an interactive format.
Prerequisites and Setup
● You should be familiar with JavaScript.
● Get OpenUI5 and place it in a location, which can be accessed in the resources folder (not necessary for SAP
Web IDE).
● Set up a folder where you would place the application content. We will refer to this folder as the “app folder”.
38
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Learning Path
● Hello World! [page 81]
● Walkthrough [page 86]
● Troubleshooting [page 205]
● Navigation and Routing [page 269]
● Data Binding [page 229]
● Testing [page 344]
● Mock Server [page 409]
● Worklist App [page 421]
Tip
Learn with openSAP:
The openSAP course Developing Web Apps with SAPUI5 introduces you to the main concepts of SAPUI5.
The JavaScript exercises for each unit will give you the technical background needed to develop your own
responsive Web apps. We’ll start from scratch with the very basics and lots of hands-on coding. As we go
through the weeks of this course, you’ll learn more about the powerful development concepts and truly master
SAPUI5.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
39
Downloading Code for a Tutorial Step
To download the code from the Demo Kit, follow these steps:
1. Choose the link in the Coding section of the tutorial step you want to work on or find the code in the Samples
section of the Demo Kit (filter by "Tutorial" to get a list of the tutorials that are available).
2. Choose the icon with the Show source code for this sample tooltip in the right-hand part of the header bar to
display all files included in this sample.
3. Choose the Download button. A download.zip file is downloaded to your local machine.
4. Extract or upload the zip file to your development environment.
5. Adjust the project configuration files to match your development environment.
6. Test the project by calling one of the HTML pages in your development environment and make sure that the
app is displaying the features exactly as shown in the preview of the step.
Adapting Code to Your Development Environment
You might have to adapt parts of the coding to your local development environment to make the app work. Please
check the following settings carefully:
● Project Path and Deployment
All tutorials assume that the app is deployed and can be accessed under a certain path on a web server. You
will not be able to run the app without a Web server as the browser does not allow you to load the required
resources locally due to security restrictions.
● OpenUI5 Resources
You can either download and deploy the runtime to your (local) Web server or reference the CDN version
located at https://openui5.hana.ondemand.com/resources/sap-ui-core.js. Some development
environments such as the SAP Web IDE also provide a local runtime for testing purposes. If you download the
code from the samples in the Demo Kit, you will have to adapt the resource path in the bootstrap section of
all HTML pages included in the project. In the tutorial code, we assume that OpenUI5 can be accessed from
the /resources path of the server.
● Accessing Remote Services
Browsers typically prevent accessing remote resources due to the Cross-Origin Resource Sharing (CORS)
policy. If you would like to call a real service or remote resources, you will have to either configure the
development environment or the remote server to accept these requests. This strongly depends on the
development environment and is described in more detail below.
Troubleshooting
If you get stuck, check the Troubleshooting section here Troubleshooting [page 924] or refer to the
Troubleshooting [page 205] tutorial.
If you can't fix the problem, try downloading the solution of the current step or the next step. This should get your
project fixed again, just don’t forget to check the resource path and the project configuration files again.
40
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Related Information
App Templates: Kick Start Your App Development [page 1011]
Demo Apps [page 465]
Development Environment
This part of the documentation gives you guidance on the most common and recommended use cases of the
installation, configuration and setup of the UI development toolkit for HTML5 (OpenUI5) development
environment.
Depending on your use case, you can choose one of the following development environments.
● App Development Using SAP Web IDE [page 43]
● App Development Using OpenUI5 [page 42]
● App Development Using SAPUI5 Tools for Eclipse [page 49]
● Node.js-Based Development Environment [page 74]
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
41
App Development Using OpenUI5
Develop apps using OpenUI5 and the development environment (editor and Web server) of your choice. You can
either download all of the sources or refer to the online version of OpenUI5.
Download OpenUI5
The default way of downloading and installing OpenUI5 is to get the runtime from the OpenUI5 home page at
http://openui5.org and deploy it on a Web server.
1. Go to http://openui5.org/download.html#stableRelease and choose Download OpenUI5 Runtime to download
a ZIP file containing the OpenUI5 sources.
2. Unzip this file and put the entire content on the Web server where your application is running (or you can even
package it within your application to deploy it along with the app).
3. In your application’s main HTML file, load OpenUI5 by referring to the resources/sap-ui-core.js file that
was contained in the ZIP file. (See Standard Variant for Bootstrapping [page 476].)
Using OpenUI5 Sources from a Content Delivery Network
If you don't want to download the files and don't want to include them in your deployment, you can use the online
version of OpenUI5. For more information, see Variant for Bootstrapping from Content Delivery Network [page
476].
Note
You can find a list of all available OpenUI5 versions here: https://openui5.hana.ondemand.com/
versionoverview.html.
Only use the Stable version for productive apps. Nevertheless, if you also want to test the Preview version, you
are very welcome to send us your feedback!
Consume OpenUI5 Using Bower
You can also use the package management system Bower to develop your application. In this case, you simply add
OpenUI5 to the bower.json file as a dependency, and it will be included automatically:
{
42
"name": "some-openui5-app",
"description": "Sample of an OpenUI5 app",
"private": true,
"dependencies": {
"openui5-sap.ui.core": "openui5/packaged-sap.ui.core",
"openui5-sap.m": "openui5/packaged-sap.m",
"openui5-themelib_sap_belize": "openui5/packaged-themelib_sap_belize"
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
}
}
}
}
You can find a sample application using this mechanism and describing its usage on GitHub at https://
github.com/SAP/openui5-sample-app.
For more information on how to install and use Bower, see the Bower home page at http://bower.io.
App Development Using SAP Web IDE
SAP Web IDE is a web-based development environment that is optimized for developing OpenUI5 complex apps
using the latest innovations, developing and extending SAP Fiori apps, developing mobile hybrid apps, and
extending SAP Web IDE with plug-ins and templates.
Key use cases:
● Develop new SAP Fiori apps and OpenUI5 apps
● Extend SAP Fiori apps
● Develop OpenUI5 mobile hybrid apps (HAT plug-in)
● Extend SAP Web IDE with new plug-ins and templates
A trial version of SAP Web IDE can be accessed through the SAP Cloud Platform.
For more information about SAP Web IDE, see the documentation for SAP Web IDE on the SAP Help Portal at
https://help.sap.com/viewer/p/SAP_Web_IDE.
Get a Trial Account and Access SAP Web IDE
Steps for creating an SAP Cloud Platform trial account
If you do not have access to SAP Web IDE, you can create a free account. To create an account, simply register an
SAP Cloud Platform trial account at https://account.hanatrial.ondemand.com/ and log on afterwards.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
43
After logging on, choose Services in the navigation bar of the SAP Cloud Platform cockpit and open the detailed
information on your SAP Web IDE by choosing the SAP Web IDE tile.
Selecting Go to Service leads you to your personal SAP Web IDE.
Note
You can bookmark this link to access SAP Web IDE later.
Start SAP Web IDE
Initial Steps in SAP Web IDE
1. Open SAP Web IDE and wait until the initialization has finished.
When you start it for the first time, you will see a home screen containing more information about SAP Web
IDE.
2. Change to the Development perspective by clicking the icon with the code symbol on the left sidebar.
You now see a folder list with an entry Workspace on the left side and an empty code editor on the right side.
44
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
3. Create your project within the Workspace folder by choosing File New Folder
+ Alt + Shift + N . Enter, for example, myProject as the folder name.
from the menu or Ctrl
Create a neo-app.json Project Configuration File
The neo-app.json file contains all project settings for SAP Web IDE and is created in the root folder of your
project. It is a JSON format file consisting of multiple configuration keys. The most important setting for you to
configure is the path where the OpenUI5 runtime is located when starting the app.
You do this using the “routes” key and defining an array of resource objects. For running an OpenUI5 tutorial, you
only need two entries - one that configures OpenUI5 to be available with the path /resources, and another one
that configures the test resources needed for the SAP Fiori launchpad integration with the path /testresources.
Create two configuration objects that contain a path, a target, and a description attribute with more
configuration settings. The path and the entryPath values will point to the location on the server where the
OpenUI5 resources will be stored.
SAP Web IDE reads these settings automatically when running the app. You can see the whole configuration file in
the code block below. Optionally, you can add the key welcomeFile to configure the entry point to your app. In
web applications, this is typically the index.html file.
Note
Depending on which SAP Web IDE version you are using, you might have to configure the project to run against
the "snapshot" version of OpenUI5, otherwise the application will be launched with the OpenUI5 release that is
delivered with SAP Web IDE. This is usually the latest version that is released publicly to customers.
You can check which version of OpenUI5 is loaded by opening the OpenUI5 debugging tools with CTRL +
SHIFT + ALT + P . If the version is too old for certain features of the tutorial, you have to add the version
attribute to the target configuration entry and set the value to snapshot.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
45
Procedure
1. Select the New File icon and enter neo-app.json as the file name.
2. Open the newly created file from the tree structure on the left side of the screen.
3. Paste the following code in the neo-app.json and select Save:
{
}
"welcomeFile": "index.html",
"routes": [
{
"path": "/resources",
"target": {
"type": "service",
"name": "sapui5",
"version": "snapshot",
"entryPath": "/resources"
},
"description": "SAPUI5 Resources"
},
{
"path": "/test-resources",
"target": {
"type": "service",
"name": "sapui5",
"entryPath": "/test-resources"
},
"description": "SAPUI5 Test Resources"
}
]
Create an index.html File
A minimalistic index.html file is needed to test the project configuration. This file contains the OpenUI5
bootstrap and an sap.m.Text control that displays the text "OpenUI5 is loaded successfully!".
1. Choose the New Folder icon in the header toolbar and enter src as the folder name.
2. Select the newly created folder and create a new index.html file inside it by choosing the New File icon.
3. Paste the following code in the newly created index.html file and select Save:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title>SAPUI5 Walkthrough</title>
<script
id="sap-ui-bootstrap"
src="/resources/sap-ui-core.js"
data-sap-ui-theme="sap_belize"
data-sap-ui-modules="sap.m.library"
data-sap-ui-compatVersion="edge"
data-sap-ui-preload="async" >
</script>
<script>
sap.ui.getCore().attachInit(function () {
new sap.m.Text({
text : "OpenUI5 is loaded successfully!"
46
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
}).placeAt("content");
});
</script>
</head>
<body class="sapUiBody" id="content">
</body>
</html>
Caution
Adapt the path where the resources are located (src="/resources/sap-ui-core.js") according to
your installation. For OpenUI5 you can use src="https://openui5.hana.ondemand.com/resources/
sap-ui-core.js".
You can use this reference to the latest stable version of OpenUI5 for the tutorial or for testing purposes, but
never use this for productive use. In an actual app, you always have to specify an OpenUI5 version explicitly.
For more information, see Variant for Bootstrapping from Content Delivery Network [page 476].
Run the App
SAP Web IDE comes with integrated testing features that let you run the app on a central server without having to
set up any additional infrastructure. You can run the app by selecting the src/index.html file and clicking the
run button in the header toolbar.
This launches the app on a central server and a testing tool that allows you to configure the screen size and
orientation of the device. This feature can be used to test apps that are specifically targeted for mobile, tablet, and
desktop devices. You can change the resolution and the orientation in the header bar very easily.
If you don't want to run the app in the testing tool, you can adjust the Run Configurations for the project:
1. Right-click any file in the project and select
Run
Run Configurations .
2. Choose + and select Web Application to add a new run configuration.
3. To save the configuration and run your project, choose Save and Run.
For more information on how to run projects, search for Configuring How to Run Projects in the SAP Web IDE
Developer Guide for the SAP Cloud Platform on the SAP Help Portal at https://help.sap.com/viewer/p/CP.
Create a Northwind Destination
Configure a destination in the SAP Cloud Platform Cockpit in order to bypass the same-origin policy of the
browser.
To be able to test your app, you can use a remote OData service that provides product data from the Northwind
demo service of the OData group.
In the navigation bar of the SAP Cloud Platform Cockpit, choose Destinations and then choose New Destination in
the toolbar.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
47
Enter the following values into the corresponding fields:
Field
Value
Name
northwind
Type
HTTP
Description
Northwind OData Service
URL
http://services.odata.org
Proxy Type
Internet
Authentication
NoAuthentication
Also, enter the following properties in the section Additional Properties:
Property
Value
WebIDEEnabled
true
WebIDESystem
Northwind
WebIDEUsage
odata_gen
With this configuration you can use the destination for any app inside SAP Web IDE. Whenever an app calls a
(local) service beginning with /destinations/northwind/*, the created destination becomes active as a
simple proxy. This helps to prevent any possible issues related to the same-origin policy of browsers.
48
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
App Development Using SAPUI5 Tools for Eclipse
Used for developing apps for simple use cases. The SAPUI5 application development tools for Eclipse provide
wizards to support you in creating apps in an easy way. With the application project wizard, the necessary
application skeleton containing views and controllers will automatically be created.
Note
We recommend this development environment only for experienced developers, and only for simple use cases.
For all other purposes, use SAP Web IDE. For more information, see App Development Using SAP Web IDE [page
43].
Installing SAPUI5 Tools for Eclipse
Required information for the installation, upgrade, and uninstallation of SAPUI5 tools.
Before you start the installation, make sure that you have the required software versions installed and that you
have downloaded the respective Software components.
Note
There are differences and specifics for the required software, software components, and installation steps
based on the product you are using.
Required Software for Installing and Running SAPUI5 Tools
To install and run SAPUI5 tools, the following software has to be installed:
● Java Runtime Environment (JRE) and Eclipse version: JRE 1.8 is mandatory, Eclipse Neon is recommended
(Eclipse Oxygen is also supported).
● Operating system: Microsoft Windows XP, Vista, 7 (32-bit or 64-bit version), 8 or 8.1, and 10
● Browser: Not relevant, except for Internet Explorer 9.0 or higher for embedded application preview
To install the SAPUI5 tools for SAP HANA, you need to install the SAP HANA Studio. For more information, see the
SAP HANA Studio Installation and Update Guide on the SAP Help Portal under https://help.sap.com/viewer/
product/SAP_HANA_PLATFORM. Make sure that the features listed in the following table are installed:
Name
Technical ID
Eclipse Faceted Project Framework (Version 3.6.1)
org.eclipse.wst.common.fproj
Eclipse Faceted Project Framework JDT Enablement (Version
3.6.1)
org.eclipse.wst.common.fproj.enablement.jdt
Eclipse Java EE Developer Tools (Version 3.6.1)
org.eclipse.jst.enterprise_ui.feature
Eclipse JavaWebEE Developer Tools (Version 3.6.1)
org.eclipse.jst.web_ui.feature
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
49
Name
Technical ID
WST Common Core (Version 3.6.1)
org.eclipse.wst.common_core.feature
WST Server Adapters
org.eclipse.wst.server_adapters
Note
Make sure that you have write permission for the directory you use for the Eclipse installation, or start Eclipse as
Administrator.
If one of the features is already available and cannot be overwritten by a newer version, only add the features
that are not yet available and leave the other features unchanged.
If you install the features from the Eclipse Release Train Update, it may be necessary to deselect the Group
Items by Category and Show only latest versions of available software checkboxes.
To install the SAPUI5 tools for SAP NetWeaver, the following software has to be installed:
● SAPUI5 ABAP Team Provider to connect to an ABAP back-end system on SAP NetWeaver 7.3 EHP1, or 7.40 or
higher
If you want to use the SAPUI5 ABAP Team Provider to connect to an ABAP backend system, the following
additional prerequisites are required:
○ User interface add-on for SAP NetWeaver 2.0
○ AiE Communication Framework
The AIE Communication Framework is part of the ABAP development tools for SAP NetWeaver. Install the
complete ABAP development tools according to the installation procedure in the Installing ABAP
Development Tools for SAP NetWeaver guide and the Configuring the ABAP Back-end for ABAP
Development Tools guide.
● Eclipse Platform
The SAPUI5 tools run on the following Eclipse versions: Neon 4.6.0 and Oxygen 4.7.0. There are two options for
the Eclipse platform: Either an 'Eclipse IDE for Java EE Developers' bundle or an 'Eclipse Classic 4.2' bundle.
Depending on the bundle you choose, you may be required to install additional features according to this list:
Name
50
Technical IDE
Min. version
Contained in Eclipse
Contained in Eclipse
JEE Bundle
Classic Bundle
Eclipse Platform
org.eclipse.platform
4.6.0 Neon
Yes
Yes
Eclipse Faceted
Project Framework
org.eclipse.wst.comm
on.fproj
3.1.0
Yes
No
Eclipse Faceted
Project Framework
JDT Enablement
org.eclipse.jst.commo
n.fproj.enablement.jdt
3.1.0
Yes
No
Eclipse Java
Development Tools
org.eclipse.jdt
3.5.0
Yes
Yes
Eclipse Java EE
Developer Tools
org.eclipse.jst.enterpri 3.1.0
se_ui.feature
Yes
No
Eclipse Java Web
Developer Tools / JST
Web UI
org.eclipse.jst.web_ui.f 3.1.0
eature
Yes
No
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Name
Technical IDE
Min. version
Contained in Eclipse
Contained in Eclipse
JEE Bundle
Classic Bundle
EMF - Eclipse
Modeling Framework
Runtime and Tools
org.eclipse.emf
2.0.0
Yes
No
Graphical Editing
Framework GEF
org.eclipse.gef
3.5.0
Yes
No
JavaScript
Development Tools
org.eclipse.wst.jsdt.fe
ature
1.1.0
Yes
No
WST Common Core
org.eclipse.wst.comm
on_core.feature
3.1.0
Yes
No
WST Server Adapters
org.eclipse.wst.server
_adapters
3.2.300
Yes
No
Note
Make sure that you have write permission for the directory you use for the Eclipse installation, or start
Eclipse as Administrator.
Required Software Components for SAPUI5 Tools
Depending on the product you are using, the following software components have to be downloaded and installed:
Product
SAP NetWeaver
Required Software Copmponents
Download the following components from the SAP Software
Download Center on SAP Support Portal atSAP Support Portal
- Software Downloads:
●
SAPUI5 TOOLS IDE PLUGIN 2.00
●
SAPUI5 TEAM PROV IDE 2.00
To find the respective download packages, search for the
above mentioned component names. The search option as
well as other options for finding the installation packages for
your component are described on the Find your Software tab
on the start page of the SAP Software Download Center.
SAP HANA
For the installation of SAPUI5 tools on SAP HANA XS, down­
load the SAPUI5 TOOLS IDE PLUGIN 1.00 component
from the SAP Software Download Center on SAP Support Por­
tal atSAP Support Portal - Software Downloads.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
51
Product
Required Software Copmponents
SAP Cloud Platform
For the installation of SAPUI5 on SAP Cloud Platform, the
SAPUI5 tools are contained in the SAP Cloud Platform Tools
and you can install them as described in https://
tools.hana.ondemand.com/#sapui5.
Installation Process for SAPUI5 Tools
To install SAPUI5 tools, proceed as follows:
1. Launch your Eclipse workbench.
2. Open the installation wizard by choosing
Help
Install New Software .
3. In the Work with field of the installation wizard, specify the target directory of the package.
To add the new installation directory, choose Add and then choose Archive to specify the location. Enter a
name for your local software site.
4. Select all features for the UI development toolkit for HTML5 and choose Next.
5. Review the feature groups to be installed and choose Next.
6. Accept the terms of the license agreement and choose Finish to initiate the installation of selected feature
groups.
7. In the Certificates dialog confirm the certificates from Eclipse.org and SAP with OK.
8. To apply the changes of the installation procedure, restart the Eclipse workbench.
9. To check, whether the installation has been successful, proceed as follows:
For application development open Eclipse and choose
File
New
Other ...
SAPUI5 Application
Development Application Project . If the installation has been successful, the New Application Project
wizard opens.
To check the team provider for the SAPUI5 ABAP repository, open Eclipse and choose
SAPUI5 Application Development
Menu Team
the list.
52
File
Application Project . Select the new project and choose
New
Other ...
Context
Share Project... . If the installation has been successful, SAPUI5 ABAP Repository appears in
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Using SAPUI5 Tools for Eclipse
The SAPUI5 application development tools for Eclipse provide wizards to support you in creating SAPUI5 apps in
an easy way. With the SAPUI5 application project wizard, the necessary application skeleton containing view(s)
and controller will automatically be created.
Creating SAPUI5 Apps
The SAPUI5tools support you in creating applications according to MVC. In this section we guide you through a
simple example. You will create a SAPUI5 application project, which includes a control, a method in the controller
and an additional view.
Utilities
With Eclipse you can make use of utilities for JavaScript development. Additionally SAPUI5 provides templates and
snippets.
● JavaScript Code Completion
The Eclipse JavaScript Development Tools (JSDT) provide an editor which parses scripts and offers code
completion functionality. The core libraries for the code completion are made available automatically.
Create an SAPUI5 Application Project
To create an SAPUI5 Application Project, you must have installed the SAPUI5 Application Development feature in
your Eclipse installation.
Procedure
1. Start the New SAPUI5 Application Project wizard in the Eclipse by choosing
Application Development
New
Other ...
SAPUI5
Application Project .
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
53
2. Enter the following project-related data:
○ Project name
○ Location (optional, prefilled from the current workspace)
○ Library 'sap.m' (default) or 'sap.ui.commons'
○ Select Create an Initial View
Views can also be added later using the SAPUI5 Application View wizard
54
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
3. Enter the following view-related data:
○ Choose the folder in which the view shall be created
○ Enter a unique name for your view
○ Choose the Development Paradigm.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
55
Results
After finishing the wizard, the system performs the following steps:
● A new dynamic Web project is created. All relevant files are created in the WebContent folder.
● A prefilled index.html is created which contains sap.ui.commons lib and sap_belize theme in the
bootstrap in case of a desktop target device and the sap.m lib and sap_mvi theme in case of mobile target
device.
● In WEB-INF folder a web.xml file is created which contains settings for resource handling and the use of
SimpleProxyServlet.
● The installed SAPUI5 UI lib plugins are automatically added to the Java build path and added to the
deployment assembly.
● TheSAPUI5 class path container (if available) is automatically added to the JavaScript include path.
● The index.html page is opened in the standard editor.
● Inside the JavaScript block of index.html, code completion is available, see JavaScript Code Completion
[page 62].
56
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
● An automatic switch to the J2EE perspective is performed.
● If you have selected the Create an Initial View option on the first page of the SAPUI5 Application Project wizard,
a view and a view controller are created and the coding to call the view is added to the index.html file.
Next Steps
Add a Control to Your View [page 57]
Implement a Method in the Controller [page 58]
Create an Additional View [page 59]
Integrate a New View [page 61]
Related Information
JavaScript Code Completion [page 62]
Use JavaScript and XML Templates [page 65]
SAPUI5 Snippets [page 66]
Linking your Eclipse Editor to the Demo Kit [page 64]
Add a Control to Your View
In your SAPUI5 application project, the first step to build your application is to add a control to your view and
implement a method to react on user interaction. In this case you create a button and implement a function to
react when the user presses it.
Procedure
To add a control to your view, add the following coding depending on the type of your view:
○ In a JS view add the following to the createContent function
var aControls = [];
var oButton = new sap.ui.commons.Button({
id : this.createId("MyButton"),
text : "Hello JS View"
});
aControls.push(oButton.attachPress(oController.doIt));
return aControls;
○ In an HTML view add the following to the template tag:
<div data-sap-ui-type="sap.ui.commons.Button" id="MyButton"
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
57
data-text="Hello HTML View" data-press="doIt">
</div>
○ In an XML view add the following coding to the core tag
<Button id="MyButton" text="Hello XML View" press="doIt"/>
○ In a JSON view add the following to the content function
"Type":"sap.ui.commons.Button",
"id":"MyButton",
"text":"Hello JSON View",
"press":"doIt"
A button is added to your view with an event that is triggered when the user presses it.
Next Steps
The doIt method, which is called in each of these view types, is implemented in the controller:
Implement a Method in the Controller [page 58]
Implement a Method in the Controller
All functions that are not directly related to the user interface should be handled in the controller to ensure clear
separation between UI and data. In this case you add a method to handle the event, which is attached to a button.
Prerequisites
You've created a button as described in: Add a Control to Your View [page 57]
Procedure
To handle this event, add the following function to the controller:
doIt : function(oEvent) { alert(oEvent.getSource().getId() + " does it!"); }
58
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Create an Additional View
An SAPUI5 application view can only be created for an SAPUI5 application project that has been created with the
SAPUI5 Application Wizard and not for other kinds of projects.
Context
● A SAPUI5 application view name needs to be unique inside the project folder.
● The specified folder for a SAPUI5 application view needs to be WebContent/<application name> or a sub
folder.
Procedure
1. Choose New
View wizard.
Other...
SAPUI5 Application Development
View
to open the New SAPUI5 Application
2. Fill in the required data:
○ Select the SAPUI5 application project, in which you want to create the view.
○ Select a folder, in which you want to store the view (default is WebContent/<application name>).
○ Enter a name for the view.
○ Select the development paradigm with which you want to develop your view.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
59
Results
When you finish the wizard, the system creates the view in the specified folder. The file name suffix indicates the
development paradigm:
● <viewname>.view.js for JavaScript views
● <viewname>.view.xml for XML views
● <viewname>.view.json for JSON views
● <viewname>.view.html for HTML views
If the corresponding index.html file contains sap.m lib in the bootstrap, that is, if the SAPUI5 application project
has been created for a mobile target device, the view contains coding for instantiating a mobile page control
sap.m.Page.
The system also creates a controller file <viewname>.controller.js with draft coding.
For JavaScript views, code completion is available, see JavaScript Code Completion [page 62].
60
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Note
If you rename the view or controller file, or move them to a different folder, the coding in the view and controller
and in the places where the view is used needs to be adapted manually.
Related Information
Views [page 653]
Integrate a New View
To integrate a new view, you can either add it to index.html or nest it into another view.
Prerequisites
If you create a new view for an existing SAPUI5 application project, the view needs to be manually called.
Procedure
To call a view, choose from the following options:
○ Directly embed the new view in the index.html page
○ All Views can be nested independent of the view type. Each view type behaves like any SAPUI5 control. The
viewName property defines, which views are embedded. To nest a view, proceed according to the given
examples:
For XML view type:
<mvc:View controllerName="sap.hcm.Address" xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m">
<Panel>
<mvc:JSView id="myJSView" viewName="sap.hcm.Bankaccount" />
</Panel>
<core:View>
For HTML views, the nested view looks as follows:
<template data-controller-name= "example.mvc.test" >
<div data-sap-ui-type= "sap.ui.core.mvc.HTMLView" id= "MyHTMLView" data-viewname= "example.mvc.test2" ></div>
<div data-sap-ui-type= "sap.ui.core.mvc.JSView" id= "MyJSView" data-view-name=
"example.mvc.test2" ></div>
<div data-sap-ui-type= "sap.ui.core.mvc.JSONView" id= "MyJSONView" data-viewname= "example.mvc.test2" ></div>
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
61
<div data-sap-ui-type= "sap.ui.core.mvc.XMLView" id= "MyXMLView" data-view-name=
"example.mvc.test2" ></div>
</template>
JavaScript Code Completion
When using the SAPUI5 tools, code completion is enabled automatically, without the tools, you need to enable it.
Automatic Code Completion for SAPUI5 Application Projects
The Eclipse JavaScript Development Tools (JSDT) provide an editor which parses scripts and offers a code
completion functionality.
Code Completion for SAPUI5 Views
For JavaScript views, code completion is available.
62
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Enabling Code Completion for Other Projects
If you are not working with a SAPUI5 application project, you can perform the following preparing steps to add the
required SAPUI5 core libraries to the JavaScript include path.
Ensure that the JavaScript Facet is set and proceed as follows:
1. Open
Project
Properties .
2. Select Project Facets.
3. If you do not see the list of all possible facets, click the link: Convert to facet form and wait a second to see all
available facets.
4. Mark JavaScript Facet on the same view.
5. Leave the project properties.
Your project now has the JavaScript facet. Now you can add the SAPUI5 core libraries. Proceed as follows:
● Open
● Choose
Project
Properties .
JavaScript
Include Path .
● Select Add JavaScript Library….
● Select SAPUI5.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
63
You should now be able to see the following JavaScript resources in your project:
Linking your Eclipse Editor to the Demo Kit
You can use Quick Fixes to display the API documentation of an SAPUI5 control in the Demo Kit.
Procedure
1. Place the cursor on the actual SAPUI5 control name in your JavaScript code or in your XMLView. The name of
the control in JavaScript code usually starts with sap.
2. To see all available Quick Fixes, press CTRL + 1 .
3. To open the API documentation of the control in the Demo Kit, choose Display in Demo Kit.
64
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Use JavaScript and XML Templates
You can add SAPUI5 control-specific templates for JavaScript code. Such templates are available, for example, in
JavaScript views of SAPUI5 application tools development.
Context
SAPUI5 provides the possibility to add SAPUI5 control specific templates for JavaScript and XML code. These
templates are available, for example, in JavaScript and XML views of SAPUI5 Application development. They are
generated during startup of the Eclipse runtime.
The templates are an overview over all available
● control properties
● aggregations
● associations and
● events
To use the JavaScript and XML templates, the SAPUI5 application development tools feature has to be installed in
your Eclipse.
Procedure
1. To insert a template, open the JavaScript editor.
2. Start typing the name of the respective control or the name of the alias, for example button.
3. Choose CRTL + SPACE and choose the control from the code completion list.
All properties and events are inserted.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
65
SAPUI5 Snippets
SAPUI5 snippets are templates and examples on how to use the SAPUI5 runtime and controls.
Context
You can add SAPUI5-specific code parts, so called SAPUI5 Snippets. SAPUI5 snippets are available as prepared
HTML pages with no separation between model, view and, controller (MVC) and they are generated during startup
of the Eclipse runtime.
Procedure
1. To open the Snippets view, proceed as follows:
a. Choose
66
Window
Show View
Other... .
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
b. In the Show View dialog, choose
General
Snippets
and confirm you selection with OK.
The Snippet view opens.
2. To insert a snippet, proceed as follows:
a. Open the index.html of your application project in the HTML editor.
b. Delete all content.
c. To insert the snippet code, double click the snippet or use drag&drop.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
67
d. Save the code and run it in the integrated browser.
68
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Note
If you have problems with incorrect rendered pages, open the external browser.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
69
Results
The page should then be displayed correctly:
70
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Running an App in the Application Preview
You access the application preview using the Web App Preview, provided with the embedded Jetty server. You can
quickly check on your application and open it in the default browser.
Procedure
1. To test the new application with the application preview in an embedded Jetty server, right-click the HTML file
or the project node and choose
Run As
Web App Preview . Everything is configured automatically.
2. To refresh after having changed a file of your application, choose Refresh on the left hand side of the preview
editor to refresh the preview.
3. To check the files of your application project in an external browser, choose Open in external browser on the
right hand side of the preview editor. This function opens the external browser which is specified in the Eclipse
preferences under General Web Browser . This is usually the default browser of your PC. For other
external browsers, you can also copy the URL from the text field of the editor to the external browser.
Depending on the libraries you use, different browsers are supported. For more information, see: Browser and
Platform Support [page 21]
Open in External Browser
Note
Before deploying an application that has been created by using the Eclipse application development tool on
a Java server, ensure to adapt the web.xml file in the <WebContent folder name>/WEB-INF folder of
the application by removing the mapping to the test resources. Test resources should only be used during
testing. Remove or comment the following lines:
<servlet-mapping>
<servlet-name>ResourceServlet</servlet-name>
<url-pattern>/test-resources/*</url-pattern>
</servlet-mapping>
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
71
Use a SimpleProxyServlet for Testing to Avoid Cross-domain
Requests
If you are testing locally in your Java Eclipse environment and you want to access an OData service in the ABAP
system, a proxy is needed to ensure the same origin policy. In an SAPUI5 application project you can use a
SimpleProxyServlet for local testing.
Caution
Be aware that due to security reasons the SimpleProxyServlet is restricted to local testing purposes only. It
can only be used for local host scenarios (accessing Gateway services to avoid cross-domain issues) and will
not work when deployed on an application server. For productive use, refer to a mature proxy servlet.
Note
If you have issues with accessing HTTPS systems via the ResourceServlet or the SimpleProxyServlet it
may be necessary to import the root certificate into the Java keystore.
.
Ideally, all OData service URLs should be in one file to make the exchange easier - either in the index.html, or in
one separate .js file which needs to be included. The application is responsible for exchanging the URLs before
checking in and after checking out to SAPUI5 Repository. You can also use the helper function getServiceUrl
for which also the application is responsible. See the following example:
<script>
//var serviceUrl = "/mypath/myservice";
//url when running on the ABAP system
//var serviceUrl = "proxy/mypath/myservice"; //url when running locally in Eclipse
var serviceUrl = getServiceUrl("/mypath/myservice");
function getServiceUrl(sServiceUrl) {
//for local testing prefix with proxy
//if you and your team use a special host name or IP like 127.0.0.1 for localhost
please adapt the if statement below
if (window.location.hostname == "localhost") {
return "proxy" + sServiceUrl;
} else {
return sServiceUrl;
}
}
</script>
As parameter for the getServiceUrl helper method, use the URL of the OData service document without
{protocol}://{host name}:{port number}, for example: /mypath/myservice.
Note
Place the script tag before the script that calls the view (sap.ui.view).
Intranet Servers
The SimpleProxyServlet allows proxy requests to an arbitrary server in the intranet.
72
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
The proxy URL that is used in the coding looks like this: proxy/<service url>.
Open the web.xml file located in the <WebContent folder name>/WEB-INF folder and configure the
parameter com.sap.ui5.proxy.REMOTE_LOCATION of the SimpleProxyServlet where the placeholders
{protocol}, {host name}, {port number} are to be exchanged by the real protocol, host name and port number:
<servlet>
<servlet-name>SimpleProxyServlet</servlet-name>
<servlet-class>com.sap.ui5.proxy.SimpleProxyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SimpleProxyServlet</servlet-name>
<url-pattern>/proxy/*</url-pattern>
</servlet-mapping>
<context-param>
<param-name>com.sap.ui5.proxy.REMOTE_LOCATION</param-name>
<param-value>{protocol}://{host name}:{port number}</param-value>
</context-param>
Internet Servers
The SimpleProxyServlet can be configured for proxy requests to internet servers in the same way as for
intranet servers. Additional proxy settings may be necessary.
As the SimpleProxyServlet automatically uses the proxy settings from your Eclipse this can be configured in
Eclipse under Window Preferences , and select
the proxy entries and the proxy bypass.
General
Network Connections . Here you can specify
For example, set Active Provider to Manual, Schema=HTTP, Host=proxy, Port=8080 under proxy entries and
localhost, *.mycompany.corp as Host under proxy bypass.
Simple Proxy Servlet - Restriction Regarding DELETE Requests
The simple proxy servlet currently does not support the sending of HTTP DELETE requests with content. This is
due to restrictions of the Java SE functionality that is used. If an HTTP DELETE request with content is sent, an
HTTP 500 result status is sent with the description: "The HttpUrlConnection used by the SimpleProxyServlet
doesn't allow to send content with the HTTP method DELETE. Due to spec having content for DELETE methods is
possible but the default implementation of the HttpUrlConnection doesn't allow this!"
For practical purposes, this restriction should have only minor effects. This is because:
● When applying a REST-like programming style, an HTTP DELETE request would use the URL to transmit which
objects should be deleted, but not the content.
● When building your own protocol that uses the content of the HTTP request, you typically use HTTP POST.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
73
Node.js-Based Development Environment
Used for modifying OpenUI5. The environment is based on Node.js, used as a server, with a build process that is
based on Grunt. This section provides information for the initial setup, development workflow, and tests execution.
The Regular Development Process
No build process is required, you can simply modify any source file and reload your browser.
This build-free development process does not deliver optimized runtime performance (for example, there are
many small requests, which would not be acceptable for remote connections). There are mainly two mechanisms
applied that adapt the sources:
● The Git repository path contains a folder with the same name as the respective control library (for example,
sap.m), which is omitted at runtime. The Node.js-based server is configured to map the locations.
● The CSS files are transformed (server-side) by the LESS preprocessor during the first request after a CSS file
has been modified. This includes mirroring for right-to-left support. After a CSS modification, this first request
to the respective library.css file will take several hundred milliseconds, depending on the amount of CSS
involved. This is the LESS processing time.
Building OpenUI5
Grunt is used to build a production version of OpenUI5. The build result is located inside the directory target/
openui5.
Usage: grunt build
Optionally, you can choose to build selected libraries only or skip copying the test-resources folder.
The build process is responsible for the following tasks:
● Creation of the bundled library.css and library-RTL.css file for all available themes
● Minification of CSS
● Minification of JavaScript (for library-preload.json files)
● Merging of the JavaScript modules of the libraries into into a single library-preload.json file
● Merging of the most important OpenUI5 core files into sap-ui-core.js (not yet optimized; minification
missing)
Note
If you encounter errors like the one below, re-do the npm install command. There might be new build tools
required that need to be downloaded first.
jit-grunt: Plugin for the "replace" task not found.
If you have installed the plugin already, please setting the static mapping.
See https://github.com/shootaroo/jit-grunt#static-mappings
Warning: Task "replace:target" not found. Use --force to continue.
74
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Installing the Node.js-Based Development Environment
Installation steps for Node.js
1. Download Node.js from http://nodejs.org and install it.
Note
The installation includes the Node Package Manager (npm).
2. Set the environment variables in the operating system settings or in the command line. You need to do this if
you are working behind an HTTP proxy:
@SET
@SET
@SET
@SET
HTTP_PROXY=http://proxy:8080
HTTPS_PROXY=http://proxy:8080
FTP_PROXY=http://proxy:8080
NO_PROXY=localhost,127.0.0.1,.mycompany.corp
Note
The example shown above is for the Windows command line. You may have to adapt the settings according
to your specific proxy configuration.
3. Install the Grunt command line interface (grunt-cli) globally with the following command: npm install
grunt-cli -g.
4. Download and install Git from http://git-scm.com/download.
5. Clone the Git repository with the following command git clone https://github.com/SAP/
openui5.git.
6. Install all npm dependencies locally. Execute the commands inside the openui5 directory as show below:
cd openui5
npm install
7. Start the server: with grunt serve.
Note
grunt serve has various configuration options you can use. For example, you can specify the parameter
--port=9090 to use a different HTTP port.
8. Point your browser to the following server where OpenUI5 is running: http://localhost:8080/
testsuite/.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
75
Testing OpenUI5
Test your development using ESLint or unit tests.
Running Static Code Checks (ESLint)
All OpenUI5 code must conform to a certain ruleset which is checked using a tool called ESLint (see Related
Information for more details).
To run an ESLint check, navigate to the root directory of the repository and execute:
grunt lint
Optionally, you can check a selected library only or even just a single file or directory.
Running the Unit Tests
The OpenUI5 unit tests are implemented using jQuery's qUnit testing framework and run by a Selenium-based
infrastructure.
To execute the unit tests, navigate to the root directory of the repository and execute:
grunt test
Caution
By default, this command runs tests for all libraries in the Google Chrome browser. For all browsers except for
Mozilla Firefox, additional Selenium web drivers need to be installed.
You can change this default behavior by specifying the following parameters:
grunt test --browsers="safari,firefox" # run tests of all libraries on Safari and
Firefox
Related Information
http://eslint.org
76
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Common Installation Issues
Common installation issues that you might face when using the node.js based development environment
Updating the build tools (task "replace:target" not found)
If you encounter errors like the one below, execute the npm install command again: there may be new build
tools required that need to be downloaded first.
jit-grunt: Plugin for the "replace" task not found.
If you have installed the plugin already, please setting the static mapping.
See https://github.com/shootaroo/jit-grunt#static-mappings
Warning: Task "replace:target" not found. Use --force to continue.
Proxy issues
grunt test will download the selenium-server-standalone.jar file when run for the first time. If you are
working behind a proxy and have no environment variables set for the proxy, this will fail when run for the first time:
selenium-server-standalone.jar not found. Downloading...
>> Error: getaddrinfo ENOTFOUND
To solve this issue, set the environment variables for the proxy server.For more information, see Installing the
Node.js-Based Development Environment [page 75].
"Browser not found" issues
Selenium needs to find the browser executable on the PATH, otherwise you will see the following error message:
firefox
Fatal error: Cannot find firefox binary in PATH. Make sure firefox is installed.
To solve this issue, add the Firefox installation folder to the PATH environment variable.
"Path to the driver executable" issues with browsers other than Mozilla Firefox
If you get the following error, remember that you need to install extra Selenium Web Drivers for all browsers apart
from Mozilla Firefox:
Fatal error: The path to the driver executable must be set by the
webdriver.chrome.driver system property;
for more information, see http://code.google.com/p/selenium/wiki/ChromeDriver.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
77
The latest version can be downloaded from http://
chromedriver.storage.googleapis.com/index.html
To solve this issue, download the Selenium driver for the respective browser and make sure the Selenium Web
Driver finds it. See the table below for specific browser instructions.
Browser
Google Chrome
Details
1.
Download the current chromedriver_*.zip from
http://
chromedriver.storage.googleapis.com/
index.htm
2. Extract the executable to a suitable location (for example,
C:\Program Files (x86)\Selenium
Drivers)
3. Include the ChromeDriver location in your PATH envi­
ronment variable
Internet Explorer (browser type "ie")
Download the driver from the following location: http://
selenium-release.storage.googleapis.com/
index.html
Note
You may have to adjust the Protected Mode settings on the
Security tab under Internet options.
Other browsers
Consult their respective driver documentation.
Undeletable folders
If you encounter source folders that cannot be deleted because a process is locking them, one possible cause may
be the Google Chrome or Internet Explorer web drivers. Check whether they are among the active processes.
Development for Hybrid Web Containers
You can develop mobile apps as hybrid app consisting of a native app wrapper, for example PhoneGap, and an
HTML viewer to display the content on the user interface.
Hybrid apps have the advantage that you can publish them in app stores. Also, by embedding the application code
and the OpenUI5 library files into the hybrid container, the user needs to install the files only once and does not
need to download them every time he starts the application. But then the library size becomes important, because
every user has to install the files, whereas in web applications, the library is deployed on a server and the user only
needs to download the required parts of the library at runtime.
78
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
To include the resources you need in your hybrid app, you can use the static mobile runtime package openui5runtime-mobile*.zip. The package is not contained in SAPUI5, but in the Open Source version OpenUI5.
The library size of these packages is rather small because the content that is most likely not needed has been
removed, for example test pages. A package contains the debug version of all JavaScript files and the optimized
and minimized version. Thus, you can use the package for productive use as well as for debugging purposes. To
use this package in an app wrapper, such as PhoneGap, unzip the package in the respective resource location of
the app development project. The app wrapper build then includes the files and makes them available at runtime.
To ensure that the file is small, it only contains the control libraries that are most likely used and not all control
libraries. Depending on the hybrid app it may be necessary to add libraries by copying them from the respective
folder of the runtime, or to delete libraries to reduce the package size and, thus, also reduce the installation size for
the user.
The file contains the following control libraries:
● sap.f
● sap.m
● sap.tnt
● sap.ui.core
● sap.ui.layout
● sap.ui.suite
● sap.ui.unified
● sap.uxap
The decision, which libraries to include or not may be disputed. It is only based on a rule of thumb, and adaptations
are required anyway for many apps.
Also, the mobile/hybrid package excludes certain types of files which are typically not needed. Your mileage may
vary, so you might need to add the respective files for the requirements of your specific app. The librarypreload.js files which contain all controls from a library to reduce the number of HTTP requests are not required
in hybrid apps because there is no HTTP latency. SAPUI5 will by default try to access them, so you might see a
failed attempt to load these files in the log file or developer tools. These error messages do not hurt, though, and
you can get rid of them by declaring that no such files exist and by setting the following configuration in the
SAPUI5 bootstrap script tag:
data-sap-ui-preload=""
Optimization of the Package Size
Although the static package is small enough to be included in hybrid apps, you can reduce the size further and
optimize the content for a specific application by deleting additional files. The following list gives some examples:
● You can delete all library folders if the respective control library is not needed. For example, in the OpenUI5
version you can delete the suite and the unified folder.
● In each of the /resources/sap/* ... */themes folders, you can delete all theme folders except the one
for the theme you are using.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
79
Note
For all JavaScript files, an optimized version and a debug (dbg) version exists. If you delete the files, make sure
that you always delete both versions. If you can do without easy debugging and want to achieve a minimum
installation size, we recommend to delete all *-dbg.js files.
You can delete further files, but the size reduction is limited and to find out the files that are not required gets
increasingly difficult.
Device Ready Event
The hybrid web container needs some time for initialization. During this time, the sending of AJAX requests is
blocked, meaning that JavaScript code stops once an AJAX request is sent and the code execution stops as well.
This leads to a UI freeze effect.
The OData model in OpenUI5 uses AJAX requests internally and the OData model initialization must therefore be
done after the hybrid container is ready to avoid a user interface freeze. After initialization, the hybrid web
containers fires an event, which is called deviceready in PhoneGap. To fix this issue, move the code where the
OData model is created and set to the core object or any other controls' model property to the deviceready
event listener.
Example:
<script>
<!-- put the following code in the beginning of the application code -->
function appReady(){
sap.ui.getCore().setModel(new sap.ui.model.odata.v2.ODataModel(<ODATA_URL>));
}
<!-- bind to the deviceready event -->
document.addEventListener("deviceready", appReady, false);
</script>
Cross Domain Restrictions
If you load data from an external server or service using AJAX, the external domain has to be configured inside the
hybrid web container to make the AJAX request go through the cross domain restriction. The following findings
result from an integration of the demo applications into PhoneGap:
● Android
If the AJAX code runs inside the webview in Android, no cross domain restriction exists. This means that you
can load data using AJAX from everywhere. The PhoneGap documentation, however, still says that the domain
needs to be configured in one XML file.
● iOS
The restriction in webview in iOS still exists and you need to add the domain that is visited using AJAX to a
whitelist file to bypass the restriction. For detailed information about the whitelist file, see the PhoneGap
documentation on the PhoneGap website.
80
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
“Hello World!”
With this tutorial, you learn how to create a simple first app in a few steps on a single HTML page.
We create an app with two pages and a navigation button to navigate between the pages.
Preview
Figure 2: Simple "Hello World" App - First Page
Figure 3: Simple "Hello World" App - Second Page
Note
You can view and download the resulting app at Demo Apps.
Coding
In the following steps, we will create an index.html file with the code below. If you directly want to try the app,
just copy and paste the code from here.
You can also launch this mini application and modify the code by creating a jsbin example at https://jsbin.com.
Caution
Adapt the path where the resources are located (<<server>>:<<port>>) according to your installation. For
OpenUI5 you can use src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js".
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
81
You can use this reference to the latest stable version of OpenUI5 for the tutorial or for testing purposes, but
never use this for productive use. In an actual app, you always have to specify an OpenUI5 version explicitly.
For more information, see Step 1: Create an HTML Page [page 83] and Variant for Bootstrapping from Content
Delivery Network [page 476].
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title>Hello World App</title>
<script src="http://<<server>>:<<port>>/resources/sap-ui-core.js"
id="sap-ui-bootstrap"
data-sap-ui-theme="sap_belize"
data-sap-ui-libs="sap.m">
</script>
<script type="text/javascript">
sap.ui.getCore().attachInit(function () {
// create a mobile app and display page1 initially
var app = new sap.m.App("myApp", {
initialPage: "page1"
});
// create the first page
var page1 = new sap.m.Page("page1", {
title : "Hello World",
showNavButton : false,
content : new sap.m.Button({
text : "Go to Page 2",
press : function () {
// navigate to page2
app.to("page2");
}
})
});
// create the second page with a back button
var page2 = new sap.m.Page("page2", {
title : "Hello Page 2",
showNavButton : true,
navButtonPress : function () {
// go back to the previous page
app.back();
}
});
// add both pages to the app
app.addPage(page1).addPage(page2);
// place the app into the HTML document
app.placeAt("content");
});
</script>
</head>
<body class="sapUiBody" id="content">
</body>
</html>
82
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Step 1: Create an HTML Page
We start with creating an HTML page for the app. There we define the meta tags, a script tag to load the OpenUI5
libraries, and a placeholder for the content of the app.
index.html (new)
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title>Hello World App</title>
<script src="http://<<server>>:<<port>>/resources/sap-ui-core.js"
id="sap-ui-bootstrap"
data-sap-ui-theme="sap_belize"
data-sap-ui-libs="sap.m">
</script>
</head>
<body class="sapUiBody" id="content">
</body>
</html>
Create a file named index.html and add following sections:
● <!DOCTYPE html>: This line tells the browser that this page is written in HTML5.
● head with the following information:
○ The meta tags <meta http-equiv="X-UA-Compatible" content="IE=edge"> to tell Microsoft
Internet Explorer to use the latest rendering engine (edge) and <meta charset="utf-8"> to tell any
browser that this file is UTF-8 encoded (assuming that you use this encoding when editing or saving the
file).
○ The title text.
○ The script tag to load and initialize OpenUI5 with the following information:
○ Location of the resources
Caution
Adapt the path where the resources are located (<<server>>:<<port>>) according to your
installation. For OpenUI5 you can use src="https://openui5.hana.ondemand.com/
resources/sap-ui-core.js".
You can use this reference to the latest stable version of OpenUI5 for the tutorial or for testing
purposes, but never use this for productive use. In an actual app, you always have to specify an
OpenUI5 version explicitly.
For more information, see Variant for Bootstrapping from Content Delivery Network [page 476].
○ You define which library is loaded, and which theme should be used. In our example, only the sap.m library
and the sap_belize theme are loaded. You can load additional libraries and themes if you like.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
83
Note
Refer to the documentation linked below for further initialization options.
● The HTML <body> tag the with ID content and class sapUiBody. This is where the content of the app will be
added in in the next steps
Now, OpenUI5 including controls is loaded and ready to use.
Related Information
Bootstrapping: Loading and Initializing [page 473]
Initialization Process [page 480]
Step 2: Initialize the App
The sap.m library provides a control called App which is meant to be the root control of an app. It initializes the
content of the body tag, sets some meta tags on the HTML document for mobile devices, and can manage
multiple pages and the animations when navigating.
index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title>Hello World App</title>
<script src="http://<server>:<port>/resources/sap-ui-core.js"
id="sap-ui-bootstrap"
data-sap-ui-theme="sap_belize"
data-sap-ui-libs="sap.m">
</script>
<script type="text/javascript">
sap.ui.getCore().attachInit(function () {
// create a mobile app and display page1 initially
var app = new sap.m.App("myApp", {
initialPage: "page1"
});
});
</script>
</head>
<body class="sapUiBody" id="content">
</body>
</html>
In a second script block, we attach a function to the global attachInit event. This function will be called as
soon as OpenUI5 is loaded and initialized. Create the app control here, and define the page that you want be
displayed initially. At this point in time, this page does not have any content.
84
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Instead of using the sap.m.App control, you could also manually call the methodjQuery.sap.initMobile() to
set up the HTML and use other full screen controls, such as sap.m.Page or sap.m.Carousel as root element of
your app.
Step 3: Add Content Pages
Apps consist of a set of pages, views, and screens between which the user can navigate. Now we add two pages to
the app.
index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title>Hello World App</title>
<script src="http://<server>:<port>/resources/sap-ui-core.js"
id="sap-ui-bootstrap"
data-sap-ui-theme="sap_belize"
data-sap-ui-libs="sap.m">
</script>
<script type="text/javascript">
sap.ui.getCore().attachInit(function () {
// create a mobile app and display page1 initially
var app = new sap.m.App("myApp", {
initialPage: "page1"
});
// create the first page
var page1 = new sap.m.Page("page1", {
title : "Hello World",
showNavButton : false,
content : new sap.m.Button({
text : "Go to Page 2",
press : function () {
// navigate to page2
app.to("page2");
}
})
});
// create the second page with a back button
var page2 = new sap.m.Page("page2", {
title : "Hello Page 2",
showNavButton : true,
navButtonPress : function () {
// go back to the previous page
app.back();
}
});
// add both pages to the app
app.addPage(page1).addPage(page2);
// place the app into the HTML document
app.placeAt("content");
});
</script>
</head>
<body class="sapUiBody" id="content">
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
85
</body>
</html>
1. Create one sap.m.Page control and set its title. To keep this example simple, the content is just one button
that navigates to the second page by calling app.to("page2") when the button is pressed. The
parameter page2 is the ID of the second page. You could also specify the animation type for the navigation.
The default is a slide animation from right to left.
Note
sap.m.Page controls can be used for the aggregation pages of the app control, but other controls could
be used as well. The page has a scrollable content section for displaying information and will create a
header and an optional footer area.
2. Create the second page that displays the Back button. The property showNavButton is set to true to display
a Back button. When it is pressed, the event handler function calls app.back(). This will bring the user back
to the main page with an an inverse animation.
3. Add both pages to the app and place the app in the content area of the HTML file that you have defined as the
body tag earlier:
app.addPage(page1).addPage(page2);
app.placeAt("content");
The app is now placed into the HTML, the app uses the whole screen size that is available on the device.
Summary
We have now created our "Hello World" app with two pages in only one HTML file.
You can run the app in any browser on any device.
For using the app on a mobile device, you upload the HTML file to a web server and call the resulting URL in your
mobile browser.
Test the navigation between both pages by choosing the buttons.
Walkthrough
In this tutorial we will introduce you to all major development paradigms of OpenUI5.
We first introduce you to the basic development paradigms like Model-View-Controller and establish a bestpractice structure of our application. We'll do this along the classic example of “Hello World” and start a new app
from scratch. Next, we'll introduce the fundamental data binding concepts of OpenUI5 and extend our app to show
a list of invoices. We'll continue to add more functionality by adding navigation, extending controls, and making our
app responsive.Finally we'll look at the testing features and the built-in support tools of OpenUI5.
86
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Preview
Tip
You don't have to do all tutorial steps sequentially, you can also jump directly to any step you want. Just
download the code from the previous step, copy it to your workspace and make sure that the application runs
by calling the webapp/index.html file.
You can view and download the samples for all steps in the in the Demo Kit at Walkthrough. Depending on your
development environment you might have to adjust resource paths and configuration entries.
For more information check the following sections of the tutorials overview page (see Get Started: Setup and
Tutorials [page 38]):
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
87
● Downloading Code for a Tutorial Step [page 40]
● Adapting Code to Your Development Environment [page 40]
Step 1: Hello World!
As you know OpenUI5 is all about HTML5. Let’s get started with building a first “Hello World” with only HTML.
Preview
Figure 4: The browser shows the text "Hello World"
Coding
You can view and download all files at Walkthrough - Step 1.
webapp/index.html (New)
<!DOCTYPE html >
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title>Walkthrough</title>
</head>
<body>
<p>Hello World</p>
</body>
</html>
Create a new folder webapp which will contain all sources of the app we will create throughout this tutorial.
Therefore, we refer to this folder as “app folder”.
88
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Now create a new root HTML file called index.html in your app folder. An HTML document consists basically of
two sections: head and body. The head part will be used by the browser to process the document. Using meta tags
we can influence the behavior of the browser.
In this case we will tell Microsoft Internet Explorer to use the latest rendering engine (edge) and the document
character set will be UTF-8. We will also give our app a title that will be displayed in the browser. Be aware that our
hard-coded title can be overruled by the app, for example to show a title in the language of the user.
The body part describes the layout of the page. In our case we simply display “Hello World” by using a p tag.
Tip
Typically, the content of the webapp folder is deployed to a Web server as an application package. When
deploying the webapp folder itself the URL for accessing the index.html file contains webapp in the path.
Conventions
● Name the root HTML file of the app index.html and locate it in the webapp folder.
Step 2: Bootstrap
Before we can do something with OpenUI5, we need to load and initialize it. This process of loading and initializing
OpenUI5 is called bootstrapping. Once this bootstrapping is finished, we simply display an alert.
Preview
Figure 5: An alert "UI5 is ready" is displayed
Coding
You can view and download all files at Walkthrough - Step 2.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
89
webapp/index.html
Note
OpenUI5 is a JavaScript library that can either be loaded from the same Web server where the app resides, or
from a different server. The code examples in this tutorial always show relative paths and assume that OpenUI5
is deployed locally in the resources folder of your Web server's root context.
If OpenUI5 is deployed somewhere else on the server or you want to use a different server, then you need to
adjust the corresponding paths in the bootstrap (here: src="/resources/sap-ui-core.js") in this tutorial
according to your own requirements. OpenUI5 can also be retrieved from the Content Delivery Network (CDN)
at https://openui5.hana.ondemand.com/resources/sap-ui-core.js.
You can use this reference to the latest stable version of OpenUI5 for the tutorial or for testing purposes, but
never use this for productive use. In an actual app, you always have to specify an OpenUI5 version explicitly.
For more information about the CDN, see Variant for Bootstrapping from Content Delivery Network [page 476].
In case you are using SAP Web IDE, you can right-click the project and select
Descriptor
New
HTML5 Application
to make the /resources… reference work. This creates the neo-app.json file, which configures
a URL mapping for this path.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title>Walkthrough</title>
<script
id="sap-ui-bootstrap"
src="/resources/sap-ui-core.js"
data-sap-ui-theme="sap_belize"
data-sap-ui-libs="sap.m"
data-sap-ui-compatVersion="edge"
data-sap-ui-preload="async" >
</script>
<script>
sap.ui.getCore().attachInit(function () {
alert("UI5 is ready");
});
</script>
</head>
<body>
<p>Hello World</p>
</body>
</html>
In this step, we load the OpenUI5 framework from our local webserver and initialize the core modules with the
following configuration options:
● The src attribute of the first <script> tag tells the browser where to find the OpenUI5 core library – it
initializes the OpenUI5 runtime and loads additional resources, such as the libraries specified in the datasap-ui-libs attribute.
● The OpenUI5 controls support different themes, we choose sap_belize as our default theme.
● We specify the required UI library sap.m containing the UI controls we need for this tutorial.
● To make use of the most recent functionality of OpenUI5 we define the compatibility version as edge.
90
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
● We configure the process of “bootstrapping” to run asynchronously.
This means that the OpenUI5 resources can be loaded simultaneously in the background for performance
reasons.
When all resources and libraries are loaded, the OpenUI5 runtime fires the global init event to signal that the
library is ready. It is a good practice to listen for this event in order to trigger your application logic only after the
event has been fired.
In the example above, we get a reference to the OpenUI5 core by calling sap.ui.getCore() and register an
anonymous callback function for the init event by calling attachInit(…) on the core. In OpenUI5 these kinds of
callback functions are often referred to as handlers, listener functions, or simply listeners. The core is a
Singleton and can be accessed from anywhere in the code.
Our anonymous callback function is executed when the bootstrap of OpenUI5 is finished and displays a native
JavaScript alert.
The sap-ui-core.js file contains a copy of jQuery, this means that you can use all jQuery features.
Related Information
Bootstrapping: Loading and Initializing [page 473]
Preload Variant for Bootstrapping [page 478]
Compatibility Version Information [page 499]
Variant for Bootstrapping from Content Delivery Network [page 476]
https://jquery.org/
Step 3: Controls
Now it is time to build our first little UI by replacing the “Hello World” text in the HTML body by the OpenUI5 control
sap.m.Text. In the beginning, we will use the JavaScript control interface to set up the UI, the control instance is
then placed into the HTML body.
Preview
Figure 6: The "Hello World" text is now displayed by a OpenUI5 control
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
91
Coding
You can view and download all files at Walkthrough - Step 3.
webapp/index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title>Walkthrough</title>
<script
id="sap-ui-bootstrap"
src="/resources/sap-ui-core.js"
data-sap-ui-theme="sap_belize"
data-sap-ui-libs="sap.m"
data-sap-ui-compatVersion="edge"
data-sap-ui-preload="async" >
</script>
<script>
sap.ui.getCore().attachInit(function () {
new sap.m.Text({
text : "Hello World"
}).placeAt("content");
});
</script>
</head>
<body class="sapUiBody" id="content">
</body>
</html>
Instead of using native JavaScript to display a dialog we want to use a simple OpenUI5 control. Controls are used
to define appearance and behavior of parts of the screen.
In the example above, the callback of the init event is where we now instantiate a OpenUI5 text control. The name
of the control is prefixed by the namespace of its control library sap.m and the options are passed to the
constructor with a JavaScript object. For our control we set the text property to the value “Hello World”.
We chain the constructor call of the control to the standard method placeAt that is used to place OpenUI5
controls inside a node of the document object model (DOM) or any other OpenUI5 control instance. We pass the
ID of a DOM node as an argument. As the target node we use the body tag of the HTML document and give it the
ID content. The class sapUiBody adds additional theme-dependent styles for displaying OpenUI5 apps.
All controls of OpenUI5 have a fixed set of properties, aggregations, and associations for configuration. You can
find their descriptions in the Demo Kit. In addition, each controls comes with a set of public functions that you can
look up in the API reference.
Don’t forget to remove the “Hello World” p.
Note
Only instances of sap.ui.core.Control or their subclasses can be rendered stand-alone and have a
placeAt function. Each control extends sap.ui.core.Element that can only be rendered inside controls.
92
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Check the API reference to learn more about the inheritance hierarchy of controls. The API documentation of
each control refers to the directly known subclasses.
Related Information
Working with Controls [page 771]
API Reference: sap.m.Text
Samples: sap.m.Text
API Reference: sap.ui.core.Control
API Reference: sap.ui.core.Element
API Reference: sap.ui.base.ManagedObject
Step 4: XML Views
Putting all our UI into the index.html file will very soon result in a messy setup and there is quite a bit of work
ahead of us. So let’s do a first modularization by putting the sap.m.Text control into a dedicated view.
OpenUI5 supports multiple view types (XML, HTML, JavaScript). We choose XML as this produces the most
readable code and will force us to separate the view declaration from the controller logic. Yet the look of our UI will
not change.
Preview
Figure 7: The "Hello World" text is now displayed by a OpenUI5 control (No visual changes to last step)
Coding
You can view and download all files at Walkthrough - Step 4.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
93
webapp/view/App.view.xml (New)
<mvc:View
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
</mvc:View>
We create a new view folder in our app and a new file for our XML view inside the app folder. The root node of the
XML structure is the view. Here, we reference the default namespace sap.m where the majority of our UI assets
are located. We define an additional sap.ui.core.mvc namespace with alias mvc, where the OpenUI5 views and
all other Model-View-Controller (MVC) assets are located.
webapp/view/App.view.xml
<mvc:View
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Text text="Hello World"/>
</mvc:View>
Inside the view tag, we add the declarative definition of our text control with the same properties as in the
previous step. The XML tags are mapped to controls and the attributes are mapped to the properties of the
control.
In OpenUI5, each control has its own ID. In the XML view above we did not specify an ID attribute, and therefore the
OpenUI5 runtime generates an own ID and adds it to the control. However, it is a good practice to set the IDs of
controls explicitly, so that controls can be identified easily.
webapp/index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title>Walkthrough</title>
<script
id="sap-ui-bootstrap"
src="/resources/sap-ui-core.js"
data-sap-ui-theme="sap_belize"
data-sap-ui-libs="sap.m"
data-sap-ui-compatVersion="edge"
data-sap-ui-preload="async"
data-sap-ui-resourceroots='{
"sap.ui.demo.walkthrough": "./"
}' >
</script>
<script>
sap.ui.getCore().attachInit(function () {
sap.ui.xmlview({
viewName : "sap.ui.demo.walkthrough.view.App"
}).placeAt("content");
94
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
});
</script>
</head>
<body class="sapUiBody" id="content">
</body>
</html>
We tell OpenUI5 core that resources in the sap.ui.demo.walkthrough namespace are located in the same
folder as index.html. This is, for example, necessary for apps that run in the SAP Fiori launchpad.
We replace the instantiation of the sap.m.Text control by our new App XML view. The view is created by a factory
function of OpenUI5 which makes sure that the view is correctly configured and can be extended by customers.
The name is prefixed with the namespace sap.ui.demo.walkthrough.view in order to uniquely identify this
resource.
Note
From this step onwards, it is necessary to run the app on a Web server. We structure the app with multiple files
that are loaded from the local file system. Without a Web server, this is prevented by the browser due to security
reasons. If the error message "sap is not defined" appears in the developer tools of the browser, you need to
check the resource path in the bootstrap.
Conventions
● View names are capitalized
● All views are stored in the view folder
● Names of XML views always end with *.view.xml
● The default XML namespace is sap.m
● Other XML namespaces use the last part of the SAP namespace as alias (for example, mvc for
sap.ui.core.mvc
Related Information
Model View Controller (MVC) [page 559]
Views [page 653]
XML View [page 653]
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
95
Step 5: Controllers
In this step, we replace the text with a button and show the “Hello World” message when the button is pressed. The
handling of the button's press event is implemented in the controller of the view.
Preview
Figure 8: A Say Hello button is added
Coding
You can view and download all files at Walkthrough - Step 5.
webapp/view/App.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.App"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Button
text="Say Hello"
press="onShowHello"/>
</mvc:View>
We add a reference to the controller, and replace the text control with a button with text “Say Hello”. The button
triggers the onShowHello event handler function when being pressed. We also have to specify the name of the
controller that is connected to the view and holds the onShowHello function by setting the controllerName
attribute of the view.
A view does not necessarily need an explicitly assigned controller. You do not have to create a controller if the view
is just displaying information and no additional functionality is required. If a controller is specified, it is instantiated
after the view is loaded.
96
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/controller/App.controller.js (New)
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function (Controller) {
"use strict";
return Controller.extend("", {
});
});
We create the folder webapp/controller and a new file App.controller.js inside. For now, we ignore the
code that manages the required modules. We will explain this part in the next step.
Note
The "use strict"; literal expression was introduced by JavaScript 1.8.5 (ECMAScript 5). It tells the browser
to execute the code in a so called “strict mode”. The strict mode helps to detect potential coding issues at an
early state at development time, that means, for example, it makes sure that variables are declared before they
are used. Thus, it helps to prevent common JavaScript pitfalls and it’s therefore a good practice to use strict
mode.
webapp/controller/App.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function (Controller) {
"use strict";
return Controller.extend("sap.ui.demo.walkthrough.controller.App", {
onShowHello : function () {
// show a native JavaScript alert
alert("Hello World");
}
});
});
We define the app controller in its own file by extending the Controller object of the OpenUI5 core. In the
beginning it holds only a single function called onShowHello that handles the button's press event by showing an
alert.
Conventions
● Controller names are capitalized
● Controllers carry the same name as the related view (if there is a 1:1 relationship)
● Event handlers are prefixed with on
● Controller names always end with *.controller.js
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
97
Related Information
Model View Controller (MVC) [page 559]
Controller [page 669]
API Reference: sap.ui.define
Step 6: Modules
In OpenUI5, resources are often referred to as modules. In this step, we replace the alert from the last exercise with
a proper Message Toast from the sap.m library. The required modules are enabled to be loaded asynchronously.
Preview
Figure 9: A message toast displays the "Hello World" message
Coding
You can view and download all files at Walkthrough - Step 6.
webapp/controller/App.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast"
], function (Controller, MessageToast) {
"use strict";
return Controller.extend("sap.ui.demo.walkthrough.controller.App", {
onShowHello : function () {
MessageToast.show("Hello World");
}
});
});
98
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
We extend the array of required modules with the fully qualified path to sap.m.MessageToast. Once both
modules, Controller and MessageToast, are loaded, the callback function is called and we can make use of
both objects by accessing the parameters passed on to the function.
This Asynchronous Module Definition (AMD) syntax allows to clearly separate the module loading from the code
execution and greatly improves the performance of the application. The browser can decide when and how the
resources are loaded prior to code execution.
Conventions
● Use sap.ui.define for controllers and all other JavaScript modules to define a global namespace. With the
namespace, the object can be addressed throughout the application.
● Use sap.ui.require for asynchronously loading dependencies but without declaring a namespace, for
example code that just needs to be executed, but does not need to be called from other code.
● Use the name of the artifact to load for naming the function parameters (without namespace).
Related Information
API Reference: sap.ui.define
API Reference: sap.ui.require
Step 7: JSON Model
Now that we have set up the view and controller, it’s about time to think about the M in MVC.
We will add an input field to our app, bind its value to the model, and bind the same value to the description of the
input field. The description will be directly updated as the user types.
Preview
Figure 10: An input field and a description displaying the value of the input field
Coding
You can view and download all files at Walkthrough - Step 7.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
99
webapp/controller/App.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast",
"sap/ui/model/json/JSONModel"
], function (Controller, MessageToast, JSONModel) {
"use strict";
return Controller.extend("sap.ui.demo.walkthrough.controller.App", {
onInit : function () {
// set data model on view
var oData = {
recipient : {
name : "World"
}
};
var oModel = new JSONModel(oData);
this.getView().setModel(oModel);
},
onShowHello : function () {
MessageToast.show("Hello World");
}
});
});
We add an init function to the controller. onInit is one of OpenUI5’s lifecycle methods that is invoked by the
framework when the controller is created, similar to a constructor function of a control.
Inside the function we instantiate a JSON model. The data for the model only contains a single property for the
“recipient”, and inside this it also contains one additional property for the name.
To be able to use this model from within the XML view, we call the setModel function on the view and pass on our
newly created model. The model is now set on the view.
The message toast is just showing the static "Hello World" message. We will show how to load a translated text
here in the next step.
webapp/view/App.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.App"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Button
text="Say Hello"
press="onShowHello"/>
<Input
value="{/recipient/name}"
description="Hello {/recipient/name}"
valueLiveUpdate="true"
width="60%"/>
</mvc:View>
We add an sap.m.Input control to the view. With this, the user can enter a recipient for the greetings. We bind its
value to a OpenUI5 model by using the declarative binding syntax for XML views:
● The curly brackets {…} indicate that data is taken from the value of the recipient's object name property.
This is called "data binding".
100
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
● /recipient/name declares the path in the model.
webapp/index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title>Walkthrough</title>
<script
id="sap-ui-bootstrap"
src="/resources/sap-ui-core.js"
data-sap-ui-theme="sap_belize"
data-sap-ui-libs="sap.m"
data-sap-ui-compatVersion="edge"
data-sap-ui-preload="async"
data-sap-ui-resourceroots='{
"sap.ui.demo.walkthrough": "./"
}' >
</script>
<script>
sap.ui.getCore().attachInit(function () {
sap.ui.xmlview({
viewName: "sap.ui.demo.walkthrough.view.App"
}).placeAt("content");
});
</script>
</head>
<body class="sapUiBody" id="content">
</body>
</html>
The binding of the value attribute is a simple binding example that contains only a binding pattern. We can also
combine texts and binding pattern to a more complex binding result as seen in the description attribute. To be able
to use the so-called complex binding syntax we have to enable it globally by setting the bootstrap parameter
data-sap-ui-compatVersion to edge. If this setting is omitted, then only standard binding syntax is allowed,
meaning "Hello {/recipient/name}" would not work anymore while "{/recipient/name}" would work just
fine.
Note
You can either use data-sap-ui-compatVersion="edge" or data-sap-ui-bindingSyntax="complex"
in the script. By setting the "edge" compatibility mode, the complex binding syntax is automatically enabled.
The edge mode automatically enables compatibility features that otherwise would have to be enabled
manually. For more information, see Compatibility Version Information [page 499].
Conventions
● Use Hungarian notation for variable names.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
101
Related Information
Model View Controller (MVC) [page 559]
Data Binding [page 673]
JSON Model [page 643]
API Reference: sap.ui.define
Step 8: Translatable Texts
In this step we move the texts of our UI to a separate resource file.
This way, they are all in a central place and can be easily translated into other languages. This process of
internationalization – in short i18n – is achieved in OpenUI5 by using a special resource model and the standard
data binding syntax, but without preceding /.
Preview
Figure 11: An input field and a description displaying the value of the input field (No visual changes to last step)
Coding
You can view and download all files at Walkthrough - Step 8.
webapp/i18n/i18n.properties (New)
showHelloButtonText=Say Hello
helloMsg=Hello {0}
We create the folder webapp/i18n and the file i18n.properties inside. The resolved bundle name is
sap.ui.demo.walkthrough.i18n, as we will see later. The properties file for texts contains name-value pairs
for each element. You can add any number of parameters to the texts by adding numbers in curly brackets to
them. These numbers correspond to the sequence in which the parameters are accessed (starting with 0).
In this tutorial we will only have one properties file. However, in real-world projects, you would have a separate file
for each supported language with a suffix for the locale, for example i18n_de.properties for German,
i18n_en.properties for English, and so on. When a user runs the app, OpenUI5 will load the language file that
fits best to the user's environment.
102
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
controller/App.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast",
"sap/ui/model/json/JSONModel",
"sap/ui/model/resource/ResourceModel"
], function (Controller, MessageToast, JSONModel, ResourceModel) {
"use strict";
return Controller.extend("sap.ui.demo.walkthrough.controller.App", {
onInit : function () {
// set data model on view
var oData = {
recipient : {
name : "World"
}
};
var oModel = new JSONModel(oData);
this.getView().setModel(oModel);
// set i18n model on view
var i18nModel = new ResourceModel({
bundleName: "sap.ui.demo.walkthrough.i18n.i18n"
});
this.getView().setModel(i18nModel, "i18n");
},
onShowHello : function () {
// read msg from i18n model
var oBundle = this.getView().getModel("i18n").getResourceBundle();
var sRecipient = this.getView().getModel().getProperty("/recipient/name");
var sMsg = oBundle.getText("helloMsg", [sRecipient]);
// show message
MessageToast.show(sMsg);
}
});
});
In the onInit function we instantiate the ResourceModel that points to the new message bundle file where our
texts are now located (i18n.properties file). The bundle name sap.ui.demo.walkthrough.i18n.i18n
consists of the application namespace sap.ui.demo.walkthrough (the application root as defined in the
index.html), the folder name i18n and finally the file name i18n without extension. The OpenUI5 runtime
calculates the correct path to the resource; in this case the path to our i18n.properties file. Next, the model
instance is set on the view as a named model with the key i18n. You use named models when you need to have
several models available in parallel.
In the onShowHello event handler function we access the i18n model to get the text from the message bundle
file and replace the placeholder {0} with the recipient from our data model. The getProperty method can be
called in any model and takes the data path as an argument. In addition, the resource bundle has a specific
getText method that takes an array of strings as second argument.
The resource bundle can be accessed with the getResourceBundle method of a ResourceModel. Rather than
concatenating translatable texts manually, we can use the second parameter of getText to replace parts of the
text with non-static data. During runtime, OpenUI5 tries to load the correct i18n_*.properties file based on
your browser settings and your locale. In our case we have only created one i18n.properties file to make it
simple. However, you can see in the network traffic of your browser’s developer tools that OpenUI5 tries to load
one or more i18n_*.properties files before falling back to the default i18n.properties file.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
103
webapp/view/App.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.App"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Button
text="{i18n>showHelloButtonText}"
press="onShowHello"/>
<Input
value="{/recipient/name}"
description="Hello {/recipient/name}"
valueLiveUpdate="true"
width="60%"/>
</mvc:View>
In the XML view, we use data binding to connect the button text to the showHelloButtonText property in the
i18n model. A resource bundle is a flat structure, therefore the preceding slash (/) can be omitted for the path.
Note
The description text is not completely localized in this example for illustration purposes. To be on the safe side,
we would have to use a similar mechanism as in the controller to use a string from the resource bundle and
replace parts of it. This can be done with the jQuery.sap.formatMessage formatter.
Furthermore, i18n files only impact client-side application texts. Texts that are loaded from back-end systems
can appear in all languages that are supported by the back-end system.
Conventions
● The resource model for internationalization is called the i18n model.
● The default filename is i18n.properties.
● Resource bundle keys are written in (lower) camelCase.
● Resource bundle values can contain parameters like {0}, {1}, {2}, …
● Never concatenate strings that are translated, always use placeholders.
● Use Unicode escape sequences for special characters.
Related Information
Resource Model [page 648]
API Reference: jQuery.sap.util.ResourceBundle
API Reference: sap.ui.model.resource.ResourceModel
Samples: sap.ui.model.resource.ResourceModel
104
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Step 9: Component Configuration
After we have introduced all three parts of the Model-View-Controller (MVC) concept, we now come to another
important structural aspect of OpenUI5.
In this step, we will encapsulate all UI assets in a component that is independent from our index.html file.
Components are independent and reusable parts used in OpenUI5 applications. Whenever we access resources,
we will now do this relatively to the component (instead of relatively to the index.html). This architectural
change allows our app to be used in more flexible environments than our static index.html page, such as in a
surrounding container like the SAP Fiori launchpad.
Preview
Figure 12: An input field and a description displaying the value of the input field (No visual changes to last step)
Coding
You can view and download all files at Walkthrough - Step 9.
Figure 13: Folder Structure for this Step
After this step your project structure will look like the figure above. We will create the Component.js file now and
modify the related files in the app.
webapp/Component.js (New)
sap.ui.define([
"sap/ui/core/UIComponent"
], function (UIComponent) {
"use strict";
return UIComponent.extend("", {
init : function () {
// call the init function of the parent
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
105
}
});
});
UIComponent.prototype.init.apply(this, arguments);
We create an initial Component.js file in the webapp folder that will hold our application setup. The init function of
the component is automatically invoked by OpenUI5 when the component is instantiated. Our component inherits
from the base class sap.ui.core.UIComponent and it is obligatory to make the super call to the init function
of the base class in the overridden init method.
webapp/Component.js
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/model/json/JSONModel",
"sap/ui/model/resource/ResourceModel"
], function (UIComponent, JSONModel, ResourceModel) {
"use strict";
return UIComponent.extend("sap.ui.demo.walkthrough.Component", {
metadata : {
rootView: {
"viewName": "sap.ui.demo.walkthrough.view.App",
"type": "XML",
"async": true,
"id": "app"
}
},
init : function () {
// call the init function of the parent
UIComponent.prototype.init.apply(this, arguments);
// set data model
var oData = {
recipient : {
name : "World"
}
};
var oModel = new JSONModel(oData);
this.setModel(oModel);
}
});
});
// set i18n model
var i18nModel = new ResourceModel({
bundleName : "sap.ui.demo.walkthrough.i18n.i18n"
});
this.setModel(i18nModel, "i18n");
The Component.js file consists of two parts now: The new metadata section that simply defines a reference to
the root view and the previously introduced init function that is called when the component is initialized. Instead
of displaying the root view directly in the index.html file as we did previously, the component will now manage
the display of the app view.
In the init function we instantiate our data model and the i18n model like we did before in the app controller. Be
aware that the models are directly set on the component and not on the root view of the component. However, as
nested controls automatically inherit the models from their parent controls, the models will be available on the
view as well.
106
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/controller/App.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast"
], function (Controller, MessageToast) {
"use strict";
return Controller.extend("sap.ui.demo.walkthrough.controller.App", {
onShowHello : function () {
// read msg from i18n model
var oBundle = this.getView().getModel("i18n").getResourceBundle();
var sRecipient = this.getView().getModel().getProperty("/recipient/name");
var sMsg = oBundle.getText("helloMsg", [sRecipient]);
// show message
MessageToast.show(sMsg);
}
});
});
Delete the onIinit function and the required modules; this is now done in the component. You now have the code
shown above.
webapp/index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title>Walkthrough</title>
<script
id="sap-ui-bootstrap"
src="/resources/sap-ui-core.js"
data-sap-ui-theme="sap_belize"
data-sap-ui-libs="sap.m"
data-sap-ui-bindingSyntax="complex"
data-sap-ui-compatVersion="edge"
data-sap-ui-preload="async"
data-sap-ui-resourceroots='{
"sap.ui.demo.walkthrough": "./"
}' >
</script>
<script>
sap.ui.getCore().attachInit(function () {
new sap.ui.core.ComponentContainer({
name : "sap.ui.demo.walkthrough"
}).placeAt("content");
});
</script>
</head>
<body class="sapUiBody" id="content">
</body>
</html>
On the index page we now instantiate the component instead of the app view. The helper method
sap.ui.core.ComponentContainer instantiates the component by searching for a Component.js file in the
namespace that is passed in as an argument. The component automatically loads the root view that we have
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
107
defined above and displays it. If you now call the index.html file, the app should still look the same, but is now
packaged into a UI component.
Conventions
● The component is named Component.js.
● Together with all UI assets of the app, the component is located in the webapp folder.
● The index.html file is located in the webapp folder if it is used productively.
Related Information
Components [page 502]
API Reference: sap.ui.core.mvc.ViewType
Samples: sap.ui.core.mvc.ViewType
Step 10: Descriptor for Applications
All application-specific configuration settings will now further be put in a separate descriptor file called
manifest.json. This clearly separates the application coding from the configuration settings and makes our app
even more flexible. For example, all SAP Fiori applications are realized as components and come with a descriptor
file in order to be hosted in the SAP Fiori launchpad.
The SAP Fiori launchpad acts as an application container and instantiates the app without having a local HTML file
for the bootstrap. Instead, the descriptor file will be parsed and the component is loaded into the current HTML
page. This allows several apps to be displayed in the same context. Each app can define local settings, such as
language properties, supported devices, and more. And we can also use the descriptor file to load additional
resources and instantiate models like our i18n resource bundle.
Preview
Figure 14: An input field and a description displaying the value of the input field (No visual changes to last step)
Coding
You can view and download all files at Walkthrough - Step 10.
108
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Caution
Automatic model instantiation is only available as of OpenUI5 version 1.30. If you are using an older version, you
can manually instantiate the resource bundle and other models of the app in the init method of the
Component.js file as we did in Step 9: Component Configuration [page 105].
webapp/manifest.json (New)
{
}
"_version": "1.8.0",
"sap.app": {
"id": "sap.ui.demo.walkthrough",
"type": "application",
"i18n": "i18n/i18n.properties",
"title": "{{appTitle}}",
"description": "{{appDescription}}",
"applicationVersion": {
"version": "1.0.0"
}
},
"sap.ui": {
"technology": "UI5",
"deviceTypes": {
"desktop": true,
"tablet": true,
"phone": true
}
},
"sap.ui5": {
"rootView": {
"viewName": "sap.ui.demo.walkthrough.view.App",
"type": "XML",
"async": true,
"id": "app"
},
"dependencies": {
"minUI5Version": "1.30",
"libs": {
"sap.m": {}
}
},
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"settings": {
"bundleName": "sap.ui.demo.walkthrough.i18n.i18n"
}
}
}
}
Note
In this tutorial, we only introduce the most important settings and parameters of the descriptor file. In SAP Web
IDE, you may get validation errors because some settings are missing - you can ignore those in this context.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
109
The content of the manifest.json file is a configuration object in JSON format that contains all global
application settings and parameters. The manifest file is called the descriptor for applications, components, and
libraries and is also referred to as “descriptor” or “app descriptor” when used for applications. It is stored in the
webapp folder and read by OpenUI5 to instantiate the component. There are three important sections defined by
namespaces in the manifest.json file:
● sap.app
The sap.app namespace contains the following application-specific attributes:
○ id (mandatory): The namespace of our application component
The ID must not exceed 70 characters. It must be unique and must correspond to the component ID/
namespace.
○ type: Defines what we want to configure, here: an application
○ i18n: Defines the path to the resource bundle file
○ title: Title of the application in handlebars syntax referenced from the app's resource bundle
○ description: Short description text what the application does in handlebars syntax referenced from the
app's resource bundle
○ applicationVersion: The version of the application to be able to easily update the application later on
● sap.ui
The sap.ui namespace contributes the following UI-specific attributes:
○ technology: This value specifies the UI technology; in our case we use OpenUI5
○ deviceTypes: Tells what devices are supported by the app: desktop, tablet, phone (all true by default)
● sap.ui5
The sap.ui5 namespace adds OpenUI5-specific configuration parameters that are automatically processed
by OpenUI5. The most important parameters are:
○ rootView: If you specify this parameter, the component will automatically instantiate the view and use it
as the root for this component
○ dependencies: Here we declare the UI libraries used in the application
○ models: In this section of the descriptor we can define models that will be automatically instantiated by
OpenUI5 when the app starts. Here we can now define the local resource bundle. We define the name of
the model "i18n" as key and specify the bundle file by namespace. As in the previous steps, the file with
our translated texts is stored in the i18n folder and named i18n.properties. We simply prefix the path
to the file with the namespace of our app. The manual instantiation in the app component's init method
will be removed later in this step.
For compatibility reasons the root object and each of the sections state the descriptor version number 1.1.0
under the internal property _version. Features might be added or changed in future versions of the
descriptor and the version number helps to identify the application settings by tools that read the descriptor.
Note
Properties of the resource bundle are enclosed in two curly brackets in the descriptor. This is not a OpenUI5
data binding syntax, but a variable reference to the resource bundle in the descriptor in handlebars syntax. The
referred texts are not visible in the app built in this tutorial but can be read by an application container like the
SAP Fiori launchpad.
110
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/i18n/i18n.properties
# App Descriptor
appTitle=Hello World
appDescription=A simple walkthrough app that explains the most important concepts
of OpenUI5
# Hello Panel
showHelloButtonText=Say Hello
helloMsg=Hello {0}
In the resource bundle we simply add the texts for the app and add comments to separate the bundle texts
semantically.
webapp/Component.js
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/model/json/JSONModel"
], function (UIComponent, JSONModel) {
"use strict";
return UIComponent.extend("sap.ui.demo.walkthrough.Component", {
metadata : {
manifest: "json"
},
init : function () {
// call the init function of the parent
UIComponent.prototype.init.apply(this, arguments);
// set data model
var oData = {
recipient : {
name : "World"
}
};
var oModel = new JSONModel(oData);
this.setModel(oModel);
}
});
});
In the component's metadata section, we now replace the rootView property with the property key manifest
and the value json. This defines a reference to the descriptor that will be loaded and parsed automatically when
the component is instantiated. We can now completely remove the lines of code containing the model instantiation
for our resource bundle. It is done automatically by OpenUI5 with the help of the configuration entries in the
descriptor. We can also remove the dependency to sap/ui/model/resource/ResourceModel and the
corresponding formal parameter ResourceModel because we will not use this inside our anonymous callback
function.
Tip
In previous versions of OpenUI5, additional configuration settings for the app, like the service configuration, the
root view, and the routing configuration, had to be added to the metadata section of the Component.js file. As
of OpenUI5 version 1.30, we recommend that you define these settings in the manifest.json descriptor file.
Apps and examples that were created based on an older OpenUI5 version still use the Component.js file for
this purpose - so it is still supported, but not recommended.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
111
Conventions
● The descriptor file is named manifest.json and located in the webapp folder.
● Use translatable strings for the title and the description of the app.
Related Information
Descriptor for Applications, Components, and Libraries [page 513]
Step 11: Pages and Panels
After all the work on the app structure it’s time to improve the look of our app. We will use two controls from the
sap.m library to add a bit more "bling" to our UI. You will also learn about control aggregations in this step.
Preview
Figure 15: A panel is now displaying the controls from the previous steps
Coding
You can view and download all files at Walkthrough - Step 11.
webapp/view/App.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.App"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
displayBlock="true">
<App>
112
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
<pages>
<Page title="{i18n>homePageTitle}">
<content>
<Panel
headerText="{i18n>helloPanelTitle}">
<content>
<Button
text="{i18n>showHelloButtonText}"
press="onShowHello"/>
<Input
value="{/recipient/name}"
description="Hello {/recipient/name}"
valueLiveUpdate="true"
width="60%"/>
</content>
</Panel>
</content>
</Page>
</pages>
</App>
</mvc:View>
We put both the input field and the button inside a containing control called sap.m.Page. The page provides an
aggregation to 0..N other controls called content. It also displays the title attribute in a header section on top of
the content. The page itself is placed into the pages aggregation of another control called sap.m.App which does
the following important things for us:
● It writes a bunch of properties into the header of the index.html that are necessary for proper display on
mobile devices.
● It offers functionality to navigate between pages with animations. We will use this soon.
In order to make the fullscreen height of the view work properly, we add the displayBlock attribute with the
value true to the view. The actual content is wrapped inside a Panel control, in order to group related content.
webapp/i18n/i18n.properties
# App Descriptor
appTitle=Hello World
appDescription=A simple walkthrough app that explains the most important concepts
of OpenUI5
# Hello Panel
showHelloButtonText=Say Hello
helloMsg=Hello {0}
homePageTitle=Walkthrough
helloPanelTitle=Hello World
We add new key/value pairs to our text bundle for the start page title and the panel title.
Conventions
● Do not make implicit use of default aggregations but always declare the aggregation names explicitly in the
view. In the example above, the content aggregation could also be omitted as the Panel control declares it as
a default, but it makes the view harder to read.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
113
Related Information
API Reference: sap.m.NavContainer
Samples: sap.m.NavContainer
API Reference: sap.m.Page
Samples: sap.m.Page
Step 12: Shell Control as Container
Now we use a shell control as container for our app and use it as our new root element. The shell takes care of
visual adaptation of the application to the device’s screen size by introducing a so-called letterbox on desktop
screens.
Preview
Figure 16: The app is now run in a shell that limits the app width
Coding
You can view and download all files at Walkthrough - Step 12.
webapp/index.html
<!DOCTYPE html>
<html>
<head>
…
<script>
sap.ui.getCore().attachInit(function () {
new sap.m.Shell({
app : new sap.ui.core.ComponentContainer({
name : "sap.ui.demo.walkthrough",
settings : {
id : "walkthrough"
}
114
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
})
}).placeAt("content");
});
</script>
</head>
<body class="sapUiBody" id="content">
</body>
</html>
The shell control is now the outermost control of our app and automatically displays a so-called letterbox, if the
screen size is larger than a certain width, all we need to do is to assign our component to the aggregation app of
the shell.
Note
We do not add the Shell control to the declarative UI definition in the XML view, because apps that run in an
external shell, like the SAP Fiori launchpad, there will already be a shell around the component UI.
There are further options to customize the shell, like setting a custom background image or color and setting a
custom logo. Check the related API reference for more details.
Related Information
API Reference: sap.m.Shell
Step 13: Margins and Paddings
Our app content is still glued to the corners of the letterbox. To fine-tune our layout, we can add margins and
paddings to the controls that we added in the previous step.
Instead of manually adding CSS to the controls, we will use the standard classes provided by OpenUI5. These
classes take care of consistent sizing steps, left-to-right support, and responsiveness.
Preview
Figure 17: The layout of the panel and its content now has margins and padding
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
115
Coding
You can view and download all files at Walkthrough - Step 13.
webapp/view/App.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.App"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
displayBlock="true">
<App>
<pages>
<Page title="{i18n>homePageTitle}">
<content>
<Panel
headerText="{i18n>helloPanelTitle}"
class="sapUiResponsiveMargin"
width="auto">
<content>
<Button
text="{i18n>showHelloButtonText}"
press="onShowHello"
class="sapUiSmallMarginEnd"/>
<Input
value="{/recipient/name}"
valueLiveUpdate="true"
width="60%"/>
<Text
text="Hello {/recipient/name}"
class="sapUiSmallMargin"/>
</content>
</Panel>
</content>
</Page>
</pages>
</App>
</mvc:View>
To layout the panel, we add the CSS class sapUiResponsiveMargin that will add some space around the panel.
We have to set the width of the panel to auto since the margin would otherwise be added to the default width of
100% and exceed the page size.
If you decrease the screen size, then you can actually see that the margin also decreases. As the name suggests,
the margin is responsive and adapts to the screen size of the device. Tablets will get a smaller margin and phones
in portrait mode will not get a margin to save space on these small screens.
Margins can be added to all kinds of controls and are available in many different options. We can even add space
between the button and the input field by adding class sapUiSmallMarginEnd to the button.
To format the output text individually, we remove the description from the input field and add a new Text control
with the same value. Here we also use a small margin to align it with the other content. Similarly, we could add the
standard padding classes to layout the inner parts of container controls such as our panel, but as it already brings
a padding by default, this is not needed here.
116
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Conventions
● Use the standard OpenUI5 CSS classes for the layout if possible.
Related Information
Using Predefined CSS Margin Classes [page 777]
Using Container Content Padding CSS Classes [page 781]
Step 14: Custom CSS and Theme Colors
Sometimes we need to define some more fine-granular layouts and this is when we can use the flexibility of CSS by
adding custom style classes to controls and style them as we like.
Preview
Figure 18: The space between the button and the input field is now smaller and the output text is bold
Caution
As stated in the Compatibility Rules, the HTML and CSS generated by OpenUI5 is not part of the public API and
may change in patch and minor releases. If you decide to override styles, you have the obligation to test and
update your modifications each time OpenUI5 is updated. A prerequisite for this is that you have control over
the version of OpenUI5 being used, for example in a standalone scenario.
Coding
You can view and download all files at Walkthrough - Step 14.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
117
webapp/css/style.css (New)
html[dir="ltr"] .myAppDemoWT .myCustomButton.sapMBtn {
margin-right: 0.125rem
}
html[dir="rtl"] .myAppDemoWT .myCustomButton.sapMBtn {
margin-left: 0.125rem
}
.myAppDemoWT .myCustomText {
display: inline-block;
font-weight: bold;
}
We create a folder css which will contain our CSS files. In a new style definition file inside the css folder we create
our custom classes combined with a custom namespace class. This makes sure that the styles will only be applied
on controls that are used within our app.
A button has a default margin of 0 that we want to override: We add a custom margin of 2px (or 0.125rem
calculated relatively to the default font size of 16px) to the button with the style class myCustomButton. We add
the CSS class sapMBtn to make our selector more specific: in CSS, the rule with the most specific selector "wins".
For right-to-left (rtl) languages, like Arabic, you set the left margin and reset the right margin as the app display is
inverted. If you only use standard OpenUI5 controls, you don't need to care about this, in this case where we use
custom CSS, you have to add this information.
In an additional class myCustomText we define a bold text and set the display to inline-block. This time we
just define our custom class without any additional selectors. We do not set a color value here yet, we will do this in
the view.
webapp/manifest.json
...
"sap.ui5": {
...
"models": {
...
},
"resources": {
"css": [
{
"uri": "css/style.css"
}
]
}
}
In the resources section of the sap.ui5 namespace, additional resources for the app can be loaded. We load the
CSS styles by defining a URI relative to the component. OpenUI5 then adds this file to the header of the HTML
page as a <link> tag, just like in plain Web pages, and the browser loads it automatically.
118
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/view/App.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.App"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
displayBlock="true">
<App class="myAppDemoWT">
<pages>
<Page title="{i18n>homePageTitle}">
<content>
<Panel
headerText="{i18n>helloPanelTitle}"
class="sapUiResponsiveMargin"
width="auto">
<content>
<Button
text="{i18n>showHelloButtonText}"
press="onShowHello"
class="myCustomButton"/>
<Input
value="{/recipient/name}"
valueLiveUpdate="true"
width="60%"/>
<FormattedText
htmlText="Hello {/recipient/name}"
class ="sapUiSmallMargin sapThemeHighlight-asColor
myCustomText"/>
</content>
</Panel>
</content>
</Page>
</pages>
</App>
</mvc:View>
The app control is configured with our custom namespace class myAppDemoWT. This class has no styling rules set
and is used in the definition of the CSS rules to define CSS selectors that are only valid for this app.
We add our custom CSS class to the button to precisely define the space between the button and the input field.
Now we have a pixel-perfect design for the panel content.
To highlight the output text, we use a FormattedText control which can be styled individually, either by using
custom CSS or with HTML code. We add our custom CSS class (myCustomText) and add a theme-dependent
CSS class to set the highlight color that is defined in the theme.
The actual color now depends on the selected theme which ensures that the color always fits to the theme and is
semantically clear. For a complete list of the available CSS class names, see CSS Classes for Theme Parameters
[page 1002].
Conventions
● Do not specify colors in custom CSS but use the standard theme-dependent classes instead.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
119
Related Information
Descriptor for Applications, Components, and Libraries [page 513]
CSS Classes for Theme Parameters [page 1002]
Creating Themable User Interfaces [page 1000]
Compatibility Rules [page 18]
API Reference: sap.ui.core.theming
Samples: sap.ui.core.theming
Step 15: Nested Views
Our panel content is getting more and more complex and now it is time to move the panel content to a separate
view. With that approach, the application structure is much easier to understand, and the individual parts of the
app can be reused.
Preview
Figure 19: The panel content is now refactored to a separate view (No visual changes to last step)
Coding
You can view and download all files at Walkthrough - Step 15.
webapp/view/App.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.App"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
displayBlock="true" >
<App class="sapUiDemoWT">
<pages>
<Page title="{i18n>homePageTitle}">
<content>
<mvc:XMLView viewName="sap.ui.demo.walkthrough.view.HelloPanel"/>
</content>
</Page>
120
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
</pages>
</App>
</mvc:View>
Instead of putting the panel and its content directly into our App view, we will move it to a new separate
HelloPanel view. We refer to this using an XMLView tag in the content aggregation of the panel.
webapp/view/HelloPanel.view.xml (New)
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.HelloPanel"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Panel
headerText="{i18n>helloPanelTitle}"
class="sapUiResponsiveMargin"
width="auto" >
<content>
<Button
text="{i18n>showHelloButtonText}"
press="onShowHello"
class="myAppDemoWT myCustomButton"/>
<Input
value="{/recipient/name}"
valueLiveUpdate="true"
width="60%"/>
<FormattedText
htmlText="Hello {/recipient/name}"
class="sapUiSmallMargin sapThemeHighlight-asColor myCustomText"/>
</content>
</Panel>
</mvc:View>
The whole content for the panel is now added to the new file HelloPanel.view.xml. We also specify the
controller for the view by setting the controllerName attribute of the XML view.
webapp/controller/HelloPanel.controller.js (New)
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast"
], function (Controller, MessageToast) {
"use strict";
return Controller.extend("sap.ui.demo.walkthrough.controller.HelloPanel", {
onShowHello : function () {
// read msg from i18n model
var oBundle = this.getView().getModel("i18n").getResourceBundle();
var sRecipient = this.getView().getModel().getProperty("/recipient/name");
var sMsg = oBundle.getText("helloMsg", [sRecipient]);
// show message
MessageToast.show(sMsg);
}
});
});
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
121
To have a reusable asset, the method onShowHello is also moved from the app controller to the HelloPanel
controller.
webapp/controller/App.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function (Controller) {
"use strict";
return Controller.extend("sap.ui.demo.walkthrough.controller.App", {
});
});
We have now moved everything out of the app view and controller. The app controller remains an empty stub for
now, we will use it later to add more functionality.
Step 16: Dialogs and Fragments
In this step, we will take a closer look at another element which can be used to assemble views: the fragment.
Fragments are light-weight UI parts (UI subtrees) which can be reused but do not have any controller. This means,
whenever you want to define a certain part of your UI to be reusable across multiple views, or when you want to
exchange some parts of a view against one another under certain circumstances (different user roles, edit mode vs
read-only mode), a fragment is a good candidate, especially where no additional controller logic is required.
A fragment can consist of 1 to n controls. At runtime, fragments placed in a view behave similar to "normal" view
content, which means controls inside the fragment will just be included into the view’s DOM when rendered. There
are of course controls that are not designed to become part of a view, for example, dialogs.
But even for these controls, fragments can be particularly useful, as you will see in a minute.
We will now add a dialog to our app. Dialogs are special, because they open on top of the regular app content and
thus do not belong to a specific view. That means the dialog must be instantiated somewhere in the controller
code, but since we want to stick with the declarative approach and create reusable artifacts to be as flexible as
possible, and because dialogs cannot be specified as views, we will create an XML fragment containing the dialog.
A dialog, after all, can be used in more than only one view of your app.
122
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Preview
Figure 20: A dialog opens when the new “Say Hello With Dialog” button is clicked
Coding
You can view and download all files at Walkthrough - Step 16.
webapp/view/HelloPanel.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.HelloPanel"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Panel
headerText="{i18n>helloPanelTitle}"
class="sapUiResponsiveMargin"
width="auto" >
<content>
<Button
text="{i18n>openDialogButtonText}"
press="onOpenDialog"
class="sapUiSmallMarginEnd"/>
<Button
text="{i18n>showHelloButtonText}"
press="onShowHello"
class="myCustomButton"/>
<Input
value="{/recipient/name}"
valueLiveUpdate="true"
width="60%"/>
<FormattedText
htmlText="Hello {/recipient/name}"
class="sapUiSmallMargin sapThemeHighlight-asColor myCustomText"/>
</content>
</Panel>
</mvc:View>
We add a new button to the view to open the dialog. It simply calls an event handler function in the controller of the
panel’s content view.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
123
webapp/view/HelloDialog.fragment.xml (New)
<core:FragmentDefinition
xmlns="sap.m"
xmlns:core="sap.ui.core" >
<Dialog
id="helloDialog"
title="Hello {/recipient/name}">
</Dialog>
</core:FragmentDefinition>
We add a new XML file to declaratively define our dialog in a fragment. The fragment assets are located in the core
namespace, so we add an xml namespace for it inside the FragmentDefinition tag.
The syntax is similar to a view, but since fragments do not have a controller this attribute is missing. Also, the
fragment does not have any footprint in the DOM tree of the app, and there is no control instance of the fragment
itself (only the contained controls). It is simply a container for a set of reuse controls.
We also add an id for our Dialog to be able to access the dialog from our HelloPanel controller.
webapp/controller/HelloPanel.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast"
], function (Controller, MessageToast) {
"use strict";
return Controller.extend("sap.ui.demo.walkthrough.controller.HelloPanel", {
onShowHello : function () {
…
},
onOpenDialog : function () {
var oView = this.getView();
var oDialog = oView.byId("helloDialog");
// create dialog lazily
if (!oDialog) {
// create dialog via fragment factory
oDialog = sap.ui.xmlfragment(oView.getId(),
"sap.ui.demo.walkthrough.view.HelloDialog");
oView.addDependent(oDialog);
}
}
});
oDialog.open();
});
If the dialog in the fragment does not exist yet, the fragment is instantiated by calling the sap.ui.xmlfragment
method with the following arguments:
● The ID of the HelloPanel view
This parameter is used to prefix the IDs inside our fragment. There, we have defined the ID helloDialog for
the Dialog control, and we can access the dialog via the view by calling oView.byId("helloDialog"). This
makes sure that even if you instantiate the same fragment in other views in the same way, each dialog will have
its unique ID that is concatenated from the view ID and the dialog ID.
124
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Using unique IDs is important, because duplicate IDs lead to errors in the framework.
● The path of the fragment definition
We add the dialog as "dependent" on the view to be connected to the lifecycle of the view’s model. A convenient
side-effect is that the dialog will automatically be destroyed when the view is destroyed. Otherwise, we would have
to destroy the dialog manually to free its resources.
webapp/i18n/i18n.properties
# App Descriptor
appTitle=Hello World
appDescription=A simple walkthrough app that explains the most important concepts
of OpenUI5
# Hello Panel
showHelloButtonText=Say Hello
helloMsg=Hello {0}
homePageTitle=Walkthrough
helloPanelTitle=Hello World
openDialogButtonText=Say Hello With Dialog
dialogCloseButtonText=Ok
The text bundle is extended by two new texts for the open button and the dialog’s close button.
Conventions
● Always use the addDependent method to connect the dialog to the lifecycle management and data binding of
the view, even though it is not added to its UI tree.
● Private functions and variables should always start with an underscore.
Related Information
Reusing UI Parts: Fragments [page 733]
Dialogs and other Popups as Fragments [page 745]
API Reference: sap.ui.core.Fragment
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
125
Step 17: Fragment Callbacks
Now that we have integrated the dialog, it's time to add some user interaction. The user will definitely want to close
the dialog again at some point, so we add a button to close the dialog and assign an event handler.
Preview
Figure 21: The dialog now has an "OK" button
Coding
You can view and download all files at Walkthrough - Step 17.
webapp/controller/HelloPanel.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast"
], function (Controller, MessageToast) {
"use strict";
return Controller.extend("sap.ui.demo.walkthrough.controller.HelloPanel", {
name");
onShowHello : function () {
// read msg from i18n model
var oBundle = this.getView().getModel("i18n").getResourceBundle();
var sRecipient = this.getView().getModel().getProperty("/recipient/
var sMsg = oBundle.getText("helloMsg", [sRecipient]);
},
// show message
MessageToast.show(sMsg);
onOpenDialog : function () {
var oView = this.getView();
var oDialog = oView.byId("helloDialog");
// create dialog lazily
if (!oDialog) {
// create dialog via fragment factory
oDialog = sap.ui.xmlfragment(oView.getId(),
"sap.ui.demo.walkthrough.view.HelloDialog", this);
126
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
}
},
});
// connect dialog to view (models, lifecycle)
oView.addDependent(oDialog);
oDialog.open();
onCloseDialog : function () {
this.getView().byId("helloDialog").close();
}
});
As previously described, fragments are pure UI reuse artifacts and do not have a controller. The third parameter of
the sap.ui.xmlfragment function is optional and allows passing in a reference to a (controller) object. For our
dialog we reference the HelloPanel controller. However, the third parameter does not necessarily have to be a
controller but can be any object.
The event handler function is put into the same controller file and it closes the dialog by accessing the internal
helper function that returns the dialog.
webapp/view/HelloDialog.fragment.xml
<core:FragmentDefinition
xmlns="sap.m"
xmlns:core="sap.ui.core" >
<Dialog
id="helloDialog"
title ="Hello {/recipient/name}">
<beginButton>
<Button
text="{i18n>dialogCloseButtonText}"
press="onCloseDialog"/>
</beginButton>
</Dialog>
</core:FragmentDefinition>
In the fragment definition, we add a button to the beginButton aggregation of the dialog. The press handler is
referring to an event handler called onCloseDialog, and since we passed in the reference to the HelloPanel
controller, the method will be invoked there when the button is pressed. The dialog has an aggregation named
beginButton as well as endButton. Placing buttons in both of these aggregations makes sure that the
beginButton is placed before the endButton on the UI. What before means, however, depends on the text
direction of the current language. We therefore use the terms begin and end as a synonym to “left” and “right". In
languages with left-to-right direction, the beginButton will be rendered left, the endButton on the right side of
the dialog footer; in right-to-left mode for specific languages the order is switched.
Related Information
Reusing UI Parts: Fragments [page 733]
Instantiation of Fragments [page 735]
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
127
Step 18: Icons
Our dialog is still pretty much empty. Since OpenUI5 is shipped with a large icon font that contains more than 500
icons, we will add an icon to greet our users when the dialog is opened.
Preview
Figure 22: An icon is now displayed in the dialog box
Coding
You can view and download all files at Walkthrough - Step 18.
webapp/view/HelloPanel.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.HelloPanel"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Panel
headerText="{i18n>helloPanelTitle}"
class="sapUiResponsiveMargin"
width="auto" >
<content>
<Button
icon="sap-icon://world"
text="{i18n>openDialogButtonText}"
press="onOpenDialog"
class="sapUiSmallMarginEnd"/>
<Button
text="{i18n>showHelloButtonText}"
press="onShowHello"
class="myCustomButton"/>
<Input
value="{/recipient/name}"
valueLiveUpdate="true"
width="60%"/>
128
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
<FormattedText
htmlText="Hello {/recipient/name}"
class="sapUiSmallMargin sapThemeHighlight-asColor myCustomText"/>
</content>
</Panel>
</mvc:View>
We add an icon to the button that opens the dialog. The sap-icon:// protocol is indicating that an icon from the
icon font should be loaded. The identifier world is the readable name of the icon in the icon font.
Tip
You can look up other icons using the Icon Explorer tool in the Demo Kit.
To call any icon, use its name as listed in the Icon Explorer in sap-icon://<iconname>.
webapp/view/HelloDialog.fragment.xml
<core:FragmentDefinition
xmlns="sap.m"
xmlns:core="sap.ui.core" >
<Dialog
id="helloDialog"
title ="Hello {/recipient/name}">
<content>
<core:Icon
src="sap-icon://hello-world"
size="8rem"
class="sapUiMediumMargin"/>
</content>
<beginButton>
<Button
text="{i18n>dialogCloseButtonText}"
press="onCloseDialog"/>
</beginButton>
</Dialog>
</core:FragmentDefinition>
In the dialog fragment, we add an icon control to the content aggregation of the dialog. Luckily, the icon font also
comes with a “Hello World” icon that is perfect for us here. We also define the size of the icon and set a medium
margin on it.
Conventions
● Always use icon fonts rather than images wherever possible, as they are scalable without quality loss (vector
graphics) and do not need to be loaded separately.
Related Information
Icon Explorer
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
129
API Reference: sap.ui.core.Icon
Samples: sap.ui.core.Icon
Step 19: Reuse Dialogs
In this step, we expand our reuse concept and invoke the dialog at component level.
In step 16, we created a dialog as fragment, to make it reusable across views or across our whole app. But we
placed the logic for retrieving the dialog instance and for opening and closing it respectively in the controller of the
HelloPanel view. Sticking to this approach would require copying and pasting the code to the controller of each
view that needs our dialog. This would cause an undesired code redundancy which we want to avoid.
In this step, we implement the solution to this problem: We expand our reuse concept and invoke the dialog at
component level.
Preview
Figure 23: The dialog is now opened by the component (no visual changes to last step)
Coding
You can view and download all files at Walkthrough - Step 19.
webapp/Component.js
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/model/json/JSONModel",
"sap/ui/demo/walkthrough/controller/HelloDialog"
], function (UIComponent, JSONModel, HelloDialog) {
"use strict";
130
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
return UIComponent.extend("sap.ui.demo.walkthrough.Component", {
metadata : {
manifest : "json"
},
init : function () {
// call the init function of the parent
UIComponent.prototype.init.apply(this, arguments);
// set data model
var oData = {
recipient : {
name : "World"
}
};
var oModel = new JSONModel(oData);
this.setModel(oModel);
// set dialog
this._helloDialog = new HelloDialog(this.getRootControl());
},
exit : function() {
this._helloDialog.destroy();
delete this._helloDialog;
},
});
});
openHelloDialog : function () {
this._helloDialog.open();
}
The dialog instantiation is refactored to a new helper object which is stored in a private property of the component.
For instantiation of the helper object, we have to pass the view instance to which the dialog is added (see method
call addDependent in the implementation of the helper object HelloDialog.js below).
We want to connect the reuse dialog to the lifecycle of the root view of the app, so we pass an instance of the root
view on to the constructor. It can be retrieved by calling the getRootControl method of the component.
Note
As defined in parameter rootView in the manifest.json file, our root view is
sap.ui.demo.walkthrough.view.App. From the component, the root view can be retrieved at runtime by
accessing the rootControl aggregation.
To be able to open the dialog from other controllers as well, we implement a reuse function openHelloDialog
which calls the open method of our helper object. By doing so, we also decouple the implementation details of the
reuse dialog from the application coding.
Up to this point we added the new property _helloDialog to the component and assigned an instance of the
HelloDialog object to it. We want to make sure that the memory allocated for this helper object is freed up when
the component is destroyed. Otherwise our application may cause memory leaks.
To do so, we use the exit hook. The OpenUI5 framework calls the function assigned to exit when destroying the
component. We call the destroy function of HelloDialog to clean up the helper class and end its lifecycle.
Nevertheless, the instance itself would still exist in the browser memory. Therefore we delete our reference to the
HelloDialog instance by calling delete this._helloDialog and the garbage collection of the browser can
clean up its memory.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
131
Note
We don't have to destroy the instance of JSONModel that we created, because we assigned it to the component
with the setModel function. The OpenUI5 framework will destroy it together with the component.
webapp/controller/HelloDialog.js (New)
sap.ui.define([
"sap/ui/base/ManagedObject"
], function (ManagedObject) {
"use strict";
return ManagedObject.extend("sap.ui.demo.walkthrough.controller.HelloDialog", {
constructor : function (oView) {
this._oView = oView;
},
exit : function () {
delete this._oView;
},
open : function () {
var oView = this._oView;
var oDialog = oView.byId("helloDialog");
// create dialog lazily
if (!oDialog) {
var oFragmentController = {
onCloseDialog : function () {
oDialog.close();
}
};
// create dialog via fragment factory
oDialog = sap.ui.xmlfragment(oView.getId(),
"sap.ui.demo.walkthrough.view.HelloDialog", oFragmentController);
// connect dialog to the root view of this component (models,
lifecycle)
oView.addDependent(oDialog);
}
oDialog.open();
}
});
});
The implementation of the HelloDialog reuse object extends an sap.ui.base.ManagedObject object to
inherit some of the core functionality of OpenUI5.
Our open method is refactored from the HelloPanel controller and instantiates our dialog fragment as in the
previous steps.
Note
We do not pass a controller as third parameter to function sap.ui.xmlfragment but a local helper object
oFragmentContoller which included the needed event handler function onCloseDialog for the fragment.
132
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
The open method now contains our dialog instantiation. The first time the open method is called, the dialog is
instantiated. The oView argument of this method is used to connect the current view to the dialog. We will call the
open method of this object later in the controller.
The onCloseDialog event handler is simply moved from the HelloPanel controller to the reuse object.
We also add an exit function, just like we did in the component, that will be called automatically when the object
is being destroyed. To free up all allocated memory in the helper object, we delete the property that holds the
reference to the view. The view itself will be destroyed by the component, so we don't need to take care for that.
webapp/controller/HelloPanel.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast"
], function (Controller, MessageToast) {
"use strict";
return Controller.extend("sap.ui.demo.walkthrough.controller.HelloPanel", {
onShowHello : function () {
// read msg from i18n model
var oBundle = this.getView().getModel("i18n").getResourceBundle();
var sRecipient = this.getView().getModel().getProperty("/recipient/
name");
var sMsg = oBundle.getText("helloMsg", [sRecipient]);
// show message
MessageToast.show(sMsg);
},
onOpenDialog : function () {
this.getOwnerComponent().openHelloDialog();
}
});
});
The onOpenDialog method now accesses its component by calling the helper method getOwnerComponent.
When calling the open method of the reuse object we pass in the current view to connect it to the dialog.
webapp/view/App.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.App"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
displayBlock="true">
<App class="myAppDemoWT">
<pages>
<Page title="{i18n>homePageTitle}">
<headerContent>
<Button
icon="sap-icon://hello-world"
press="onOpenDialog"/>
</headerContent>
<content>
<mvc:XMLView
viewName="sap.ui.demo.walkthrough.view.HelloPanel"/>
</content>
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
133
</Page>
</pages>
</App>
</mvc:View>
We add a button to the header area of the app view to show the reuse of the hello world dialog. When pressing the
button the dialog will be opened as with the button that we previously created in the panel.
webapp/controller/App.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function (Controller) {
"use strict";
return Controller.extend("sap.ui.demo.walkthrough.controller.App", {
onOpenDialog : function () {
this.getOwnerComponent().openHelloDialog();
}
});
});
We add the method onOpenDialog also to the app controller so that the dialog will open with a reference to the
current view.
Conventions
● Put all assets that are used across multiple controllers in separate modules.
Related Information
Memory Management on https://developer.mozilla.org
API Reference: sap.ui.base.ManagedObject
134
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Step 20: Aggregation Binding
Now that we have established a good structure for our app, it's time to add some more functionality. We start
exploring more features of data binding by adding some invoice data in JSON format that we display in a list below
the panel.
Preview
Figure 24: A list of invoices is displayed below the panel
Coding
You can view and download all files at Walkthrough - Step 20.
webapp/Invoices.json (New)
{
"Invoices": [
{
"ProductName": "Pineapple",
"Quantity": 21,
"ExtendedPrice": 87.2000,
"ShipperName": "Fun Inc.",
"ShippedDate": "2015-04-01T00:00:00",
"Status": "A"
},
{
"ProductName": "Milk",
"Quantity": 4,
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
135
"ExtendedPrice": 9.99999,
"ShipperName": "ACME",
"ShippedDate": "2015-02-18T00:00:00",
"Status": "B"
},
{
"ProductName": "Canned Beans",
"Quantity": 3,
"ExtendedPrice": 6.85000,
"ShipperName": "ACME",
"ShippedDate": "2015-03-02T00:00:00",
"Status": "B"
},
{
}
]
"ProductName": "Salad",
"Quantity": 2,
"ExtendedPrice": 8.8000,
"ShipperName": "ACME",
"ShippedDate": "2015-04-12T00:00:00",
"Status": "C"
},
{
"ProductName": "Bread",
"Quantity": 1,
"ExtendedPrice": 2.71212,
"ShipperName": "Fun Inc.",
"ShippedDate": "2015-01-27T00:00:00",
"Status": "A"
}
The invoices file simply contains five invoices in a JSON format that we can use to bind controls against them in
the app. JSON is a very lightweight format for storing data and can be directly used as a data source for OpenUI5
applications.
webapp/manifest.json
{
…
"sap.ui5": {
"rootView": "sap.ui.demo.walkthrough.view.App",
[…]
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"settings": {
"bundleName": "sap.ui.demo.walkthrough.i18n.i18n"
}
},
"invoice": {
"type": "sap.ui.model.json.JSONModel",
"uri": "Invoices.json"
}
}
}
}
We add a new model invoice to the sap.ui5 section of the descriptor. This time we want a JSONModel, so we
set the type to sap.ui.model.json.JSONModel. The uri key is the path to our test data relative to the
component. With this little configuration our component will automatically instantiate a new JSONModel which
136
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
loads the invoice data from the Invoices.json file. Finally, the instantiated JSONModel is put onto the
component as a named model invoice. The named model is then visible throughout our app.
Note
Automatic model instantiation is only available as of OpenUI5 version 1.30. If you are using an older version, you
can manually instantiate the resource bundle and other models of the app in the onInit method of the
Component.js file as we did for the resource bundle in Step 9: Component Configuration [page 105].
webapp/view/App.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.App"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
displayBlock="true" >
<App class="myAppDemoWT">
<pages>
<Page title="{i18n>homePageTitle}">
<headerContent>
<Button
icon="sap-icon://hello-world"
press="onOpenDialog"/>
</headerContent>
<content>
<mvc:XMLView viewName="sap.ui.demo.walkthrough.view.HelloPanel"/>
<mvc:XMLView viewName="sap.ui.demo.walkthrough.view.InvoiceList"/>
</content>
</Page>
</pages>
</App>
</mvc:View>
In the app view we add a second view to display our invoices below the panel.
webapp/view/InvoiceList.view.xml (New)
<mvc:View
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<List
headerText="{i18n>invoiceListTitle}"
class="sapUiResponsiveMargin"
width="auto"
items="{invoice>/Invoices}" >
<items>
<ObjectListItem
title="{invoice>Quantity} x {invoice>ProductName}"/>
</items>
</List>
</mvc:View>
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
137
The new view is displaying a list control with a custom header text. The item aggregation of the list is bound to the
root path Invoices of the JSON data. And since we defined a named model, we have to prefix each binding
definition with the identifier invoice>.
In the items aggregation, we define the template for the list that will be automatically repeated for each invoice of
our test data. More precisely, we use an ObjectListItem to create a control for each aggregated child of the
items aggregation. The title property of the list item is bound to properties of a single invoice. This is achieved
by defining a relative path (without / in the beginning). This works because we have bound the items aggregation
via items={invoice>/Invoices} to the invoices.
webapp/i18n/i18n.properties
# App Descriptor
appTitle=Hello World
appDescription=A simple walkthrough app that explains the most important concepts
of OpenUI5
# Hello Panel
showHelloButtonText=Say Hello
helloMsg=Hello {0}
homePageTitle=Walkthrough
helloPanelTitle=Hello World
openDialogButtonText=Say Hello With Dialog
dialogCloseButtonText=Ok
# Invoice List
invoiceListTitle=Invoices
In the text bundle the title of the list is added.
Related Information
Lists [page 1242]
API Reference: sap.m.List
Samples: sap.m.List
List Binding (Aggregation Binding) [page 680]
Step 21: Data Types
The list of invoices is already looking nice, but what is an invoice without a price assigned? Typically prices are
stored in a technical format and with a '.' delimiter in the data model. For example, our invoice for pineapples has
the calculated price 87.2 without a currency. We are going to use the OpenUI5 data types to format the price
properly, with a locale-dependent decimal separator and two digits after the separator.
138
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Preview
Figure 25: The list of invoices with prices and number units
Coding
You can view and download all files at Walkthrough - Step 21.
webapp/view/InvoiceList.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.InvoiceList"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<List
headerText="{i18n>invoiceListTitle}"
class="sapUiResponsiveMargin"
width="auto"
items="{invoice>/Invoices}">
<items>
<ObjectListItem
title="{invoice>Quantity} x {invoice>ProductName}"
number="{
parts: [{path: 'invoice>ExtendedPrice'}, {path: 'view>/currency'}],
type: 'sap.ui.model.type.Currency',
formatOptions: {
showMeasure: false
}
}"
numberUnit="{view>/currency}"/>
</items>
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
139
</List>
</mvc:View>
We add a price to our invoices list in the view by adding the number and numberUnit attributes to the
ObjectListItem control, then we apply the currency data type on the number by setting the type attribute of
the binding syntax to sap.ui.model.type.Currency.
As you can see above, we are using a special binding syntax for the number property of the ObjectListItem.
This binding syntax makes use of so-called "Calculated Fields", which allows the binding of multiple properties
from different models to a single property of a control. The properties bound from different models are called
“parts”. In the example above, the property of the control is number and the bound properties (“parts”) retrieved
from two different models are invoice>ExtendedPrice and view>/currency.
We want to display the price in Euro, and typically the currency is part of our data model on the back end. In our
case this is not the case, so we need to define it directly in the app. We therefore add a controller for the invoice list,
and use the currency property as the second part of our binding syntax. The Currency type will handle the
formatting of the price for us, based on the currency code. In our case, the price is displayed with 2 decimals.
Additionally, we set the formatting option showMeasure to false. This hides the currency code in the property
number, because it is passed on to the ObjectListItem control as a separate property numberUnit.
webapp/controller/InvoiceList.controller.js (New)
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel"
], function (Controller, JSONModel) {
"use strict";
return Controller.extend("sap.ui.demo.walkthrough.controller.InvoiceList", {
onInit : function () {
var oViewModel = new JSONModel({
currency: "EUR"
});
this.getView().setModel(oViewModel, "view");
}
});
});
To be able to access the currency code that is not part of our data model, we define a view model in the controller
of the invoice list. It is a simple JSON model with just one key currency and the value EUR. This can be bound to
the formatter of the number field. View models can hold any configuration options assigned to a control to bind
properties such as the visibility.
Conventions
● Use data types instead of custom formatters whenever possible.
140
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Related Information
Calculated Fields for Data Binding [page 715]
Custom Formatter Functions [page 718]
API Reference: sap.ui.model.type
API Reference: sap.ui.model.type.Currency
Samples: sap.ui.model.type.Currency
Step 22: Expression Binding
Sometimes the predefined types of OpenUI5 are not flexible enough and you want to do a simple calculation or
formatting in the view - that is where expressions are really helpful. We use them to format our price according to
the current number in the data model.
Preview
Figure 26: The price is now formatted according to its number
Coding
You can view and download all files at Walkthrough - Step 22.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
141
webapp/view/InvoiceList.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.InvoiceList"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<List
headerText="{i18n>invoiceListTitle}"
class="sapUiResponsiveMargin"
width="auto"
items="{invoice>/Invoices}" >
<items>
<ObjectListItem
title="{invoice>Quantity} x {invoice>ProductName}"
number="{
parts: [{path: 'invoice>ExtendedPrice'}, {path: 'view>/currency'}],
type: 'sap.ui.model.type.Currency',
formatOptions: {
showMeasure: false
}
}"
numberUnit="{view>/currency}"
numberState="{= ${invoice>ExtendedPrice} > 50 ? 'Error' : 'Success' }"/>
</items>
</List>
</mvc:View>
We add the property numberState in our declarative view and introduce a new binding syntax that starts with =
inside the brackets. This symbol is used to initiate a new binding syntax, it's called an expression and can do
simple calculation logic like the ternary operator shown here.
The condition of the operator is a value from our data model. A model binding inside an expression binding has to
be escaped with the $ sign as you can see in the code. We set the state to 'Error' (the number will appear in
red) if the price is higher than 50 and to ‘Success’ (the number will appear in green) otherwise.
Expressions are limited to a particular set of operations that help formatting the data such as Math expression,
comparisons, and such. You can lookup the possible operations in the documentation.
Conventions
● Only use expression binding for trivial calculations.
Related Information
Expression Binding [page 719]
142
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Step 23: Custom Formatters
If we want to do a more complex logic for formatting properties of our data model, we can also write a custom
formatting function. We will now add a localized status with a custom formatter, because the status in our data
model is in a rather technical format.
Preview
Figure 27: A status is now displayed with a custom formatter
Coding
You can view and download all files at Walkthrough - Step 23.
webapp/model/formatter.js (New)
sap.ui.define([], function () {
"use strict";
return {
statusText: function (sStatus) {
var resourceBundle =
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
143
this.getView().getModel("i18n").getResourceBundle();
switch (sStatus) {
case "A":
return resourceBundle.getText("invoiceStatusA");
case "B":
return resourceBundle.getText("invoiceStatusB");
case "C":
return resourceBundle.getText("invoiceStatusC");
default:
return sStatus;
}
}
};
});
Our new formatter file is located in the model folder of the app, because formatters are working on data
properties and format them for display on the UI. So far we did not have any model-related artifacts, except for the
Invoices.json file, we will now add the folder webapp/model to our app. This time we do not extend from any
base object but just return a JavaScript object with our formatter functions inside the sap.ui.define call.
Function statusText gets the technical status from the data model as input parameter and returns a humanreadable text that is read from the resourceBundle file.
webapp/controller/InvoiceList.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel",
"sap/ui/demo/walkthrough/model/formatter"
], function (Controller, JSONModel, formatter) {
"use strict";
return Controller.extend("sap.ui.demo.walkthrough.controller.InvoiceList", {
formatter: formatter,
onInit : function () {
var oViewModel = new JSONModel({
currency: "EUR"
});
this.getView().setModel(oViewModel, "view");
}
});
});
To load our formatter functions, we have to add it to the InvoiceList.controller.js. In this controller, we
first add a dependency to our custom formatter module. The controller simply stores the loaded formatter
functions in the local property formatter to be able to access them in the view.
webapp/view/InvoiceList.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.InvoiceList"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<List
headerText="{i18n>invoiceListTitle}"
144
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
class="sapUiResponsiveMargin"
width="auto"
items="{invoice>/Invoices}">
<items>
<ObjectListItem
title="{invoice>Quantity} x {invoice>ProductName}"
number="{
parts: [{path: 'invoice>ExtendedPrice'}, {path: 'view>/
currency'}],
type: 'sap.ui.model.type.Currency',
formatOptions: {
showMeasure: false
}
}"
numberUnit="{view>/currency}"
numberState="{=
${invoice>ExtendedPrice} > 50 ? 'Error' :
'Success' }">
<firstStatus>
<ObjectStatus text="{
path: 'invoice>Status',
formatter: '.formatter.statusText'
}"/>
</firstStatus>
</ObjectListItem>
</items>
</List>
</mvc:View>
We add a status using the firstStatus aggregation to our ObjectListItem that will display the status of our
invoice. The custom formatter function is specified with the reserved property formatter of the binding syntax. A
"." in front of the formatter name means that the function is looked up in the controller of the current view. There
we defined a property formatter that holds our formatter functions, so we can access it
by .formatter.statusText.
webapp/i18n/i18.properties
# App Descriptor
appTitle=Hello World
appDescription=A simple walkthrough app that explains the most important concepts
of OpenUI5
# Hello Panel
showHelloButtonText=Say Hello
helloMsg=Hello {0}
homePageTitle=Walkthrough
helloPanelTitle=Hello World
openDialogButtonText=Say Hello With Dialog
dialogCloseButtonText=Ok
# Invoice List
invoiceListTitle=Invoices
invoiceStatusA=New
invoiceStatusB=In Progress
invoiceStatusC=Done
We add three new entries to the resource bundle that reflect our translated status texts. These texts are now
displayed below the number attribute of the ObjectListItem dependent on the status of the invoice.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
145
Related Information
Custom Formatter Functions [page 718]
Step 24: Filtering
In this step, we add a search field for our product list and define a filter that represents the search term. When
searching, the list is automatically updated to show only the items that match the search term.
Preview
Figure 28: A search field is displayed above the list
Coding
You can view and download all files at Walkthrough - Step 24.
146
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/view/InvoiceList.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.InvoiceList"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<List
id="invoiceList"
class="sapUiResponsiveMargin"
width="auto"
items="{invoice>/Invoices}" >
<headerToolbar>
<Toolbar>
<Title text="{i18n>invoiceListTitle}"/>
<ToolbarSpacer/>
<SearchField width="50%" search="onFilterInvoices"/>
</Toolbar>
</headerToolbar>
<items>
<ObjectListItem>
…
</ObjectListItem/>
</items>
</List>
</mvc:View>
The view is extended by a search control that we add to the list of invoices. We also need to specify an ID
invoiceList for the list control to be able to identify the list from the event handler function
onFilterInvoices that we add to the search field. In addition, the search field is part of the list header and
therefore, each change on the list binding will trigger a rerendering of the whole list, including the search field.
The headerToolbar aggregation replaces the simple title property that we used before for our list header. A
toolbar control is way more flexible and can be adjusted as you like. We are now displaying the title on the left side
with a sap.m.Title control, a spacer, and the sap.m.SearchField on the right.
webapp/controller/InvoiceList.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel",
"sap/ui/demo/walkthrough/model/formatter",
"sap/ui/model/Filter",
"sap/ui/model/FilterOperator"
], function (Controller, JSONModel, formatter, Filter, FilterOperator) {
"use strict";
return Controller.extend("sap.ui.demo.walkthrough.controller.InvoiceList", {
formatter: formatter,
onInit : function () {
var oViewModel = new JSONModel({
currency: "EUR"
});
this.getView().setModel(oViewModel, "view");
},
onFilterInvoices : function (oEvent) {
// build filter array
var aFilter = [];
var sQuery = oEvent.getParameter("query");
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
147
sQuery));
});
});
}
if (sQuery) {
aFilter.push(new Filter("ProductName", FilterOperator.Contains,
}
// filter binding
var oList = this.byId("invoiceList");
var oBinding = oList.getBinding("items");
oBinding.filter(aFilter);
We load two new dependencies for the filtering. The filter object will hold our configuration for the filter action and
the FilterOperator is a helper type that we need in order to specify the filter.
In the onFilterInvoices function we construct a filter object from the search string that the user has typed in
the search field. Event handlers always receive an event argument that can be used to access the parameters that
the event provides. In our case the search field defines a parameter query that we access by calling
getParameter(“query”) on the oEvent parameter.
If the query is not empty, we add a new filter object to the still empty array of filters. However, if the query is empty,
we filter the binding with an empty array. This makes sure that we see all list elements again. We could also add
more filters to the array, if we wanted to search more than one data field. In our example, we just search in the
ProductName path and specify a filter operator that will search for the given query string.
The list is accessed with the ID that we have specified in the view, because the control is automatically prefixed by
the view ID, we need to ask the view for the control with the helper function byId. On the list control we access the
binding of the aggregation items to filter it with our newly constructed filter object. This will automatically filter
the list by our search string so that only the matching items are shown when the search is triggered. The filter
operator FilterOperator.Contains is not case-sensitive.
Related Information
API Reference: sap.ui.model.Filter
API Reference: sap.ui.model.FilterOperator
API Reference: sap.m.SearchField
148
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Step 25: Sorting and Grouping
To make our list of invoices even more user-friendly, we sort it alphabetically instead of just showing the order from
the data model. Additionally, we introduce groups and add the company that ships the products so that the data is
easier to consume.
Preview
Figure 29: The list is now sorted and grouped by the shipping company
Coding
You can view and download all files at Walkthrough - Step 25.
webapp/view/InvoiceList.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.InvoiceList"
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
149
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<List
id="invoiceList"
class="sapUiResponsiveMargin"
width="auto"
items="{
path : 'invoice>/Invoices',
sorter : {
path : 'ProductName'
}
}" >
<headerToolbar>
...
</headerToolbar>
<items>
...
</items>
</List>
</mvc:View>
We add a declarative sorter to our binding syntax. As usual, we transform the simple binding syntax to the object
notation, specify the path to the data, and now add an additional sorter property. We specify the data path by
which the invoice items should be sorted, the rest is done automatically. By default, the sorting is ascending, but
you could also add a property descending with the value true inside the sorter property to change the sorting
order.
If we run the app now we can see a list of invoices sorted by the name of the products.
webapp/view/InvoiceList.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.InvoiceList"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<List
id="invoiceList"
class="sapUiResponsiveMargin"
width="auto"
items="{
path : 'invoice>/Invoices',
sorter : {
path : 'ShipperName',
group : true
}
}">
<headerToolbar>
<Toolbar>
<Title text="{i18n>invoiceListTitle}"/>
<ToolbarSpacer/>
<SearchField width="50%" search="onFilterInvoices"/>
</Toolbar>
</headerToolbar>
<items>
…
</items>
</List>
</mvc:View>
We modify the view and add a different sorter, or better; we change the sorter and set the attribute group to true.
We also specify the path to the ShipperName data field. This groups the invoice items by the shipping company.
150
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
As with the sorter, no further action is required. The list and the data binding features of OpenUI5 will do the trick
to display group headers automatically and categorize the items in the groups. We could define a custom grouping
function if we wanted by setting the groupHeaderFactory property, but the result looks already fine.
Related Information
API Reference: sap.ui.model.Sorter
Sample: List - Grouping
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
151
Step 26: Remote OData Service
So far we have only worked with local JSON data, but now we will access a real OData service. Instead of
implementing an own OData service we will simply use the publicly available Northwind OData service to visualize
remote data. You will be surprised how little needs to be changed in order to make this work!
Preview
Figure 30: Products from the OData invoices test service are now shown within our app
152
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Coding
You can view and download all files at Walkthrough - Step 26.
webapp/manifest.json
{
"_version": "1.8.0",
"sap.app": {
...
"ach": "CA-UI5-DOC",
"dataSources": {
"invoiceRemote": {
"uri": "https://services.odata.org/V2/Northwind/Northwind.svc/",
"type": "OData",
"settings": {
"odataVersion": "2.0"
}
}
}
},
"sap.ui": {
...
},
"sap.ui5": {
...
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"settings": {
"bundleName": "sap.ui.demo.walkthrough.i18n.i18n"
}
},
"invoice": {
"dataSource": "invoiceRemote"
}
}
}
}
In the sap.app section of the descriptor file, we add a data source configuration. With the invoiceRemote, key
we specify a configuration object that allows automatic model instantiation. We specify the type of the service
(OData) and the model version (2.0). In this step, we want to use the publicly available Northwind OData service
located at https://services.odata.org/V2/Northwind/Northwind.svc/. Therefore, the URI points to the
official Northwind OData service.
Note
We are referencing the Northwind OData service via HTTPS. However, the certificate might not be trusted. Thus,
make sure that you call the URL https://services.odata.org/V2/Northwind/Northwind.svc/ directly
in your browser and accept the certificate once, before you continue.
In the models section, we replace the content of the invoice model. This key is still used as model name when
the model is automatically instantiated during the component initialization. However, the invoiceRemote value of
the dataSource key is a reference to the data source section that we specified above. This configuration allows
the component to retrieve the technical information for this model during the start-up of the app.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
153
Our component now automatically creates an instance of sap.ui.model.odata.v2.ODataModel according to
the settings we specified above, and makes it available as model named invoice. If you want to have a default
model on the component, you can change the name of the model to an empty string in the descriptor file.
Automatically instantiated models can be retrieved by calling this.getModel in the component. In the
controllers of component-based apps you can call this.getView().getModel() to get the automatically
instantiated model. For retrieving a named model you have to pass on the model name defined in the descriptor
file to getModel, this means, in the component you would call this.getModel("invoice") to get our
automatically generated invoice model that we defined in the descriptor.
When using the data source invoiceRemote, the ODataModel fetches the data from the real Northwind OData
service. The invoices we receive from the Northwind OData service have identical properties as the JSON data we
used previously (except for the status propertythat is not available in the Northwind OData service).
You can now try to run the app and see what happens - we will see some errors in the browser’s console:
Figure 31: Violations of the same-origin policy in Google Chrome
Due to the so called same-origin policy, browsers deny AJAX requests to service endpoints in case the domain/
subdomain, protocol, or port differ from the app’s domain/subdomain, protocol, or port.
The browser refuses to connect to a remote URL directly for security reasons and we need a workaround:
In Google Chrome, you can easily disable same-origin policy of Chrome by running Chrome with the following
command: [here-your-path-to-chrome-installation-dir]\chrome.exe --disable-web-security
--user-data-dir. Make sure that all instances of Chrome are closed before you run the command above. This
will allow all web sites to break out of the same-origin policy and connect to the remote service directly.
Caution
Be aware that it’s a security risk in case you run Chrome this way for surfing on the internet. However, it
also allows you to avoid the need of setting up a proxy at development time or for testing purposes.
For productive apps this approach is therefore not recommended. If you have the same issue in a productive
app, check Request Fails Due to Same-Origin Policy (Cross-Origin Resource Sharing (CORS)) [page 990] in the
First-Aid Kit [page 985].
After disabling the same-origin policy in your browser, you can now run the app again. This time you can see all
kinds of invoices retrieved from a real back end. In case you still have issues, just continue with the next step.
There, we will switch to local mock data.
Related Information
OData Home Page
API Reference: sap.ui.model.odata.v2.ODataModel
154
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Step 27: Mock Server Configuration
We just ran our app against a real service, but for developing and testing our app we do not want to rely on the
availability of the “real” service or put additional load on the system where the data service is located.
This system is the so-called back-end system that we will now simulate with an OpenUI5 feature called mock
server. It serves local files, but it simulates a back-end system more realistically than just loading the local data. We
will also change the model instantiation part so that the model is configured in the descriptor and instantiated
automatically by OpenUI5. This way, we do not need to take care of the model instantiation in the code.
Preview
Figure 32: The list of invoices is now served by the Mock Server
Coding
You can view and download all files at Walkthrough - Step 27.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
155
Figure 33: Folder Structure for this Step
The folder structure of our app project is clearly separating test and productive files after this step. The new test
folder now contains a new HTML page mockServer.html which will launch our application in test mode without
calling the real service.
The new localService folder contains a metadata.xml service description file for OData, the mockserver.js
file that simulates a real service with local data, and the mockdata subfolder that contains the local test data
(Invoices.json).
webapp/test/mockServer.html (New)
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title>SAPUI5 Walkthrough - Test page</title>
<script
id="sap-ui-bootstrap"
src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-theme="sap_belize"
data-sap-ui-libs="sap.m"
data-sap-ui-compatVersion="edge"
data-sap-ui-preload="async"
data-sap-ui-resourceroots='{
"sap.ui.demo.walkthrough": "../"
}'>
</script>
<script>
sap.ui.getCore().attachInit(function () {
sap.ui.require([
"sap/ui/demo/walkthrough/localService/mockserver",
"sap/m/Shell",
"sap/ui/core/ComponentContainer"
], function (mockserver, Shell, ComponentContainer) {
mockserver.init();
new Shell({
156
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
app : new ComponentContainer({
name : "sap.ui.demo.walkthrough",
settings : {
id : "walkthrough"
}
})
}).placeAt("content");
});
});
</script>
</head>
<body class="sapUiBody" id="content">
</body>
</html>
We copy the index.html to a separate file in the webapp/test folder and name it mockServer.html. We will
now use this file to run our app in test mode with mock data loaded from a JSON file. Test pages should not be
placed in the application root folder but in a test folder to clearly separate productive and test coding.
From this point on, you have two different entry pages: One for the real “connected” app (index.html) and one
for local testing (mockServer.html). You can freely decide if you want to do the next steps on the real service
data or on the local data within the app.
Note
If no connection to the real service is available or the proxy configuration from the previous step does not work,
you can always use the mockServer.html file. This will display the app with simulated test data. The
index.html file will always load the data from a remote server. If the request fails, the list of invoices will stay
empty.
webapp/test/mockServer.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title>Walkthrough - Test page</title>
<script
id="sap-ui-bootstrap"
src="/resources/sap-ui-core.js"
data-sap-ui-theme="sap_belize"
data-sap-ui-libs="sap.m"
data-sap-ui-bindingSyntax="complex"
data-sap-ui-compatVersion="edge"
data-sap-ui-preload="async"
data-sap-ui-resourceroots='{
"sap.ui.demo.walkthrough": "../"
}'>
</script>
<script>
sap.ui.getCore().attachInit(function () {
sap.ui.require([
"sap/ui/demo/walkthrough/localService/mockserver",
"sap/m/Shell",
"sap/ui/core/ComponentContainer"
], function (mockserver, Shell, ComponentContainer) {
mockserver.init();
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
157
new Shell({
app : new ComponentContainer({
height: "100%",
name: "sap.ui.demo.walkthrough"
})
}).placeAt("content");
});
});
</script>
</head>
<body class="sapUiBody" id="content">
</body>
</html>
We modify the mockServer.html file and change the page title to distinguish it from the productive start page. In
the bootstrap the data-sap-ui-resourceroots property is also changed slightly because the
mockServer.html file is not directly inside the webapp folder anymore.
Additionally, we switch the initialization of the component to the sap.ui.require syntax, because we do now
load more additional files required for the startup of our app. The first dependency is a file called mockserver.js
that will be located in the localService folder later. We also switch to the dependencies provided by the
require statement for instantiating a Shell and ComponentContainer instead of using full namespaces to
sap.m. Shell and sap.ui.core.ComponentContainer.
The new mockserver.js resource that we just loaded and are about to implement is our local test server. Its
init method is immediately called before we actually define the component. This way we can catch all requests
that would go to the “real” service and process them locally by our test server when launching the app with the
mockServer.html file. The component itself does not "know" that it will now run in test mode.
The mock server does not need to be called from anywhere else in our code so we use sap.ui.require to load
dependencies asynchronously without defining a global namespace.
webapp/localService/mockdata/Invoices.json (New)
[
{
"ProductName": "Pineapple",
"Quantity": 21,
"ExtendedPrice": 87.2000,
"ShipperName": "Fun Inc.",
"ShippedDate": "2015-04-01T00:00:00",
"Status": "A"
},
{
"ProductName": "Milk",
"Quantity": 4,
"ExtendedPrice": 9.99999,
"ShipperName": "ACME",
"ShippedDate": "2015-02-18T00:00:00",
"Status": "B"
},
{
"ProductName": "Canned Beans",
"Quantity": 3,
"ExtendedPrice": 6.85000,
"ShipperName": "ACME",
"ShippedDate": "2015-03-02T00:00:00",
"Status": "B"
158
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
]
},
{
"ProductName": "Salad",
"Quantity": 2,
"ExtendedPrice": 8.8000,
"ShipperName": "ACME",
"ShippedDate": "2015-04-12T00:00:00",
"Status": "C"
},
{
"ProductName": "Bread",
"Quantity": 1,
"ExtendedPrice": 2.71212,
"ShipperName": "Fun Inc.",
"ShippedDate": "2015-01-27T00:00:00",
"Status": "A"
}
The Invoices.json file is similar to our previous file in the webapp folder. Just copy the content and remove the
outer object structure with the key invoices so that the file consists of one flat array of invoice items. This file will
automatically be read by our server later in this step.
Remove the old Invoices.json file from the webapp folder, it is no longer used.
webapp/localService/metadata.xml (New)
<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">
<edmx:DataServices m:DataServiceVersion="1.0" m:MaxDataServiceVersion="3.0"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/
dataservices/metadata">
<Schema Namespace="NorthwindModel" xmlns="http://schemas.microsoft.com/ado/
2008/09/edm">
<EntityType Name="Invoice">
<Key>
<PropertyRef Name="ProductName"/>
<PropertyRef Name="Quantity"/>
<PropertyRef Name="ShipperName"/>
</Key>
<Property Name="ShipperName" Type="Edm.String" Nullable="false"
MaxLength="40" FixedLength="false"
Unicode="true"/>
<Property Name="ProductName" Type="Edm.String" Nullable="false"
MaxLength="40" FixedLength="false"
Unicode="true"/>
<Property Name="Quantity" Type="Edm.Int16" Nullable="false"/>
<Property Name="ExtendedPrice" Type="Edm.Decimal" Precision="19"
Scale="4"/>
</EntityType>
</Schema>
<Schema Namespace="ODataWebV2.Northwind.Model" xmlns="http://
schemas.microsoft.com/ado/2008/09/edm">
<EntityContainer Name="NorthwindEntities"
m:IsDefaultEntityContainer="true" p6:LazyLoadingEnabled="true"
xmlns:p6="http://schemas.microsoft.com/ado/2009/02/edm/
annotation">
<EntitySet Name="Invoices" EntityType="NorthwindModel.Invoice"/>
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
159
The metadata file contains information about the service interface and does not need to be written manually. It
can be accessed directly from the “real” service by calling the service URL and adding $metadata at the end (e.g.
in our case http://services.odata.org/V2/Northwind/Northwind.svc/$metadata). The mock server
will read this file to simulate the real OData service, and will return the results from our local source files in the
proper format so that it can be consumed by the app (either in XML or in JSON format).
For simplicity, we have removed all content from the original Northwind OData metadata document that we do not
need in our scenario. We have also added the status field to the metadata since it is not available in the real
Northwind service.
webapp/localService/mockserver.js (New)
sap.ui.define([
"sap/ui/core/util/MockServer"
], function (MockServer) {
"use strict";
return {
init: function () {
// create
var oMockServer = new MockServer({
rootUri: "/destinations/northwind/V2/Northwind/Northwind.svc/"
});
var oUriParameters = jQuery.sap.getUriParameters();
// configure
MockServer.config({
autoRespond: true,
autoRespondAfter: oUriParameters.get("serverDelay") || 1000
});
// simulate
var sPath =
jQuery.sap.getModulePath("sap.ui.demo.walkthrough.localService");
oMockServer.simulate(sPath + "/metadata.xml", sPath + "/mockdata");
// start
oMockServer.start();
}
};
});
Now that we have added the OData service description file metadata.xml file, we can write the code to initialize
the mock server which will then simulate any OData request to the real Northwind server.
We load the MockServer module as a dependency and create a helper object that defines an init method to
start the server. This method is called before the component initialization in the mockServer.html file above. The
init method creates a MockServer instance with the same URL as the real service calls.
The URL in configuration parameter rootURI will now be served by our test server instead of the real service. It
matches the URL of our data source in the descriptor file. Next, we set two global configuration settings that tell
the server to respond automatically and introduce a delay of one second to imitate a typical server response time.
Otherwise, we would have to call the respond method on the MockServer manually to simulate the call.
To simulate a service, we can simply call the simulate method on the MockServer instance with the path to our
newly created metadata.xml. This will read the test data from our local file system and set up the URL patterns
that will mimic the real service.
Finally, we call start on oMockServer. From this point, each request to the URL pattern rootURI will be processed
by the MockServer. If you switch from the index.html file to the mockServer.html file in the browser, you can
160
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
now see that the test data is displayed from the local sources again, but with a short delay. The delay can be
specified with the URI parameter serverDelay, the default value is one second.
This approach is perfect for local testing, even without any network connection. This way your development does
not depend on the availability of a remote server, i.e. to run your tests.
Try calling the app with the index.html file and the mockServer.html file to see the difference. If the real
service connection cannot be made, for example when there is no network connection, you can always fall back to
the local test page.
Note
The URI of the invoiceRemote data source in the descriptor points to our destination configured for SAP Web
IDE (see previous step). We assume this destination to be available. In any other development environment, you
need to use a local proxy for the request to the service as described in the previous step. This is important when
you call the application with the index.html file, otherwise the call to the remote service will fail.
Conventions
● The webapp/test folder contains non-productive code only.
● Mock data and the script to start the MockServer are stored in the webapp/localService folder.
● The script to start the MockServer is called mockserver.js.
Related Information
Mock Server [page 891]
API Reference: sap.ui.core.util.MockServer
Step 28: Unit Test with QUnit
Now that we have a test folder in the app, we can start to increase our test coverage.
Actually, every feature that we added to the app so far, would require a separate test case. We have totally
neglected this so far, so let’s add a simple unit test for our custom formatter function from Step 23. We will test if
the long text for our status is correct by comparing it with the texts from our resource bundle.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
161
Preview
Figure 34: A unit test for our formatters is now available
Coding
You can view and download all files at Walkthrough - Step 28.
Figure 35: Folder Structure for this Step
We add a new folder unit under the test folder and a model subfolder where we will place our formatter unit test.
The folder structure matches the app structure to easily find the corresponding unit tests.
162
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/test/unit/model/formatter.js
sap.ui.require(
[
"sap/ui/demo/walkthrough/model/formatter",
"sap/ui/model/resource/ResourceModel",
"sap/ui/thirdparty/sinon",
"sap/ui/thirdparty/sinon-qunit"
],
function (formatter, ResourceModel) {
"use strict";
QUnit.module("Formatting functions", {
beforeEach: function () {
this._oResourceModel = new ResourceModel({
bundleUrl : jQuery.sap.getModulePath("sap.ui.demo.walkthrough",
"/i18n/i18n.properties")
});
},
afterEach: function () {
this._oResourceModel.destroy();
}
});
QUnit.test("Should return the translated texts", function (assert) {
// Arrange
// this.stub() does not support chaining and it always return the right
data
// even if with wrong or empty parameter passed to it
var oModel = this.stub();
oModel.withArgs("i18n").returns(this._oResourceModel);
var oViewStub = {
getModel: oModel
};
var oControllerStub = {
getView: this.stub().returns(oViewStub)
};
// System under test
var fnIsolatedFormatter = formatter.statusText.bind(oControllerStub);
// Assert
assert.strictEqual(fnIsolatedFormatter("A"), "New", "The long text for
status A is correct");
assert.strictEqual(fnIsolatedFormatter("B"), "In Progress", "The long
text for status B is correct");
assert.strictEqual(fnIsolatedFormatter("C"), "Done", "The long text for
status C is correct");
assert.strictEqual(fnIsolatedFormatter("Foo"), "Foo", "The long text
for status Foo is correct");
});
}
);
We create a new formatter.js file under webapp/test/unit/model where the unit test for the custom
formatter is implemented. The formatter file that we want to test is loaded as a dependency. We also need a
dependency to the ResourceModel, because we want to check if the translated texts are correct. Additionally, we
need to load SinonJS to create a stub for the dependencies in the formatter function.
The formatter file just contains one QUnit module for our formatter function. It instantiates our ResourceBundle
with the localized texts in the beforeEach function and destroys it again in the afterEach function. These
functions are called before and after each test is executed.
Next is our unit test for the formatter function. In the implementation of the statusText function that we created
in step 23 we access the ResourceBundle with the following queued call: var resourceBundle =
this.getView().getModel("i18n").getResourceBundle();.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
163
Since we do not want to test the controller, the view, or the model functionality, we first remove the dependencies
by replacing these calls with empty hulls with the help of SinonJS and its stub method. This happens in the
Arrange section of the unit test. SinonJS injects a stub method for all objects so we can simply call this.stub()
to create a new stub for any behavior we need to mock.
Test stubs are functions with pre-programmed behavior. They support the full SinonJS test spy API in addition to
methods which can be used to alter the stub’s behavior. If this part is a bit confusing have a look at the official
SinonJS documentation for test spies or ignore it for now, it will become clear later on.
Then we bind our stub to the statusText formatter by calling the bind function of JavaScript. The this pointer
is now bound to our controller stub when the function is invoked using the variable fnIsolatedFormatter and
we can still pass in arguments as we like. This happens in the "system under test" part of the test.
Finally we perform our assertions. We check each branch of the formatter logic by invoking the isolated formatter
function with the values that we expect in the data model (A, B, C, and everything else). We strictly compare the
result of the formatter function with the hard-coded strings that we expect from the resource bundle and give a
meaningful error message if the test should fail. We hard-code the strings here to identify issues with the resource
bundle properties. If a property was missing, the test would still be successful if we check against the real value
(that would be an empty string on both sides) from the resource bundle.
webapp/test/unit/unitTests.qunit.html (New)
<!DOCTYPE html>
<html>
<head>
<title>Unit tests for Walkthrough</title>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<meta charset="utf-8">
<script id="sap-ui-bootstrap"
src="/resources/sap-ui-core.js"
data-sap-ui-resourceroots='{
"sap.ui.demo.walkthrough": "../../",
"test.unit": "./"
}'>
</script>
<script>
jQuery.sap.require("sap.ui.qunit.qunit-css");
jQuery.sap.require("sap.ui.thirdparty.qunit");
jQuery.sap.require("sap.ui.qunit.qunit-junit");
jQuery.sap.require("sap.ui.qunit.qunit-coverage");
// model tests
jQuery.sap.require("test.unit.model.formatter");
</script>
</head>
<body>
<div id="content"></div>
<h1 id="qunit-header">Unit tests for Walkthrough</h1>
<h2 id="qunit-banner"></h2>
<h2 id="qunit-userAgent"></h2>
<div id="qunit-testrunner-toolbar"></div>
<ol id="qunit-tests"></ol>
<div id="qunit-fixture"></div>
</body>
</html>
The so-called QUnit test suite is an HTML page that triggers all QUnit tests for the application. Most of it is
generating the layout of the result page that you can see in the preview and we won’t further explain these parts
but focus on the application parts instead.
164
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Let’s start with the namespaces. Since we are now in the webapp/test/unit folder, we actually need to go up
two levels to get the src folder again. This namespace can be used inside the tests to load and trigger application
functionality. The test.unit namespace is simply a reference to the current folder so that all QUnit files can be
loaded with the test namespace.
After requiring some basic QUnit functionality (for technical reasons we cannot do this asynchronously with our
sap.ui.require syntax), we load and thus execute our formatter. Other QUnit tests can be added here as well. If
we now open the webapp/test/unit/unitTests.qunit.html file in the browser, we should see our test
running and verifying the formatter logic.
Conventions
● All unit tests are placed in the webapp/test/unit folder of the app.
● Files in the test suite end with *.qunit.html.
● The unitTests.qunit.html file triggers all unit tests of the app.
● A unit test should be written for formatters, controller logic, and other individual functionality.
● All dependencies are replaced by stubs to test only the functionality in scope.
Related Information
Unit Testing with QUnit [page 853]
QUnit Home Page
Sinon.JS Home Page
Step 29: Integration Test with OPA
If we want to test interaction patterns or more visual features of our app, we can also write an integration test.
We haven’t thought about testing our interaction with the app yet, so in this step we will check if the dialog actually
opens when we click the “Say Hello with Dialog” button. We can easily do this with OPA5, a feature of OpenUI5 that
is easy to set up and is based on JavaScript and QUnit. Using integration and unit tests and running them
consistently in a continuous integration (CI) environment, we can make sure that we don’t accidentally break our
app or introduce logical errors in existing code.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
165
Preview
Figure 36: An OPA test opens the "Hello" dialog from step 16
Coding
You can view and download all files at Walkthrough - Step 29.
166
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Figure 37: Folder Structure for this Step
We add a new folder integration below the test folder, where we put our new test cases. Page objects that help
structuring such integration tests are put in the pages subfolder that we also create now.
webapp/test/integration/navigationJourney.js (New)
/*global QUnit*/
/*global opaTest*/
sap.ui.require([
"sap/ui/test/opaQunit"
], function () {
"use strict";
QUnit.module("Navigation");
opaTest("Should open the hello dialog", function (Given, When, Then) {
// Arrangements
Given.iStartMyAppInAFrame(jQuery.sap.getResourcePath("sap/ui/demo/app/
test", ".html"));
//Actions
When.onTheAppPage.iPressTheSayHelloWithDialogButton();
});
});
// Assertions
Then.onTheAppPage.iShouldSeeTheHelloDialog().
and.iTeardownMyAppFrame();
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
167
Let’s start with the journey first. A journey consists of a series of integration tests that belong to the same
context such as navigating through the app. Similar to the QUnit test implementation, OPA5 uses QUnit, that's why
we first set up a QUnit module Navigation that will be displayed on our result page.
The function opaTest is the main aspect for defining integration tests with OPA. Its parameters define a test name
and a callback function that gets executed with the following OPA5 helper objects to write meaningful tests that
read like a user story.
● Given
On the given object we can call arrangement functions like iStartMyAppInAFrame to load our app in a
separate iFrame for integration testing.
● When
Contains custom actions that we can execute to get the application in a state where we can test the expected
behavior.
● Then
Contains custom assertions that check a specific constellation in the application and the teardown function
that removes our iFrame again.
In our test, we create a very simple test that starts the test page in an iFrame. In the app, we trigger a click a button
and expect that the dialog is opened afterwards. Finally, we remove the iFrame again from our test page.
As you can see, the test case reads like a user story, we actually do not need the implementation of the methods
yet to understand the meaning of the test case. This approach is called "Behavior Driven Development" or simply
BDD and is popular in "Agile Software Development".
webapp/test/integration/pages/App.js (New)
sap.ui.require([
"sap/ui/test/Opa5"
],
function (Opa5) {
"use strict";
Opa5.createPageObjects({
onTheAppPage: {
actions: {
iPressTheSayHelloWithDialogButton: function () {
return this.waitFor({
controlType: "sap.m.Button",
success: function (aButtons) {
aButtons[0].$().trigger("tap");
},
errorMessage: "Did not find the helloDialogButton button on
the app page"
});
}
},
assertions: {
iShouldSeeTheHelloDialog: function () {
return this.waitFor({
controlType: "sap.m.Dialog",
success: function () {
// we set the view busy, so we need to query the parent
of the app
Opa5.assert.ok(true, "The dialog is open");
},
errorMessage: "Did not find the dialog control"
});
168
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
});
});
}
}
}
The implementation of the page object holds the helper functions we just called in our journey. We require OPA5
from the sap.ui.test namespace and define a page object with the helper function createPageObjects. We
pass in an object with the key of our page onTheAppPage and two sections: actions and assertions.
In the actions section of the page object we define a function to click the "Hello" dialog button. This is done in
OPA5 with a waitFor statement, it is basically a loop that checks for the conditions defined as parameters. If the
conditions are met, the success callback is executed, if the test fails because the conditions have not been met,
the text in the errorMessage property is displayed on the result page.
We define a waitFor statement that checks for controls of type sap.m.Button. As soon as a button is found on
the app page the success handler is executed and we use jQuery to trigger a tap event on the first button that we
found. This should open the HelloDialog similar to clicking on the button manually.
In the assertions section we define another waitFor statement that checks if a sap.m.Dialog control is existing
in the DOM of the app. When the dialog has been found, the test is successful and we can immediately confirm by
calling an ok statement with a meaningful message.
webapp/test/integration/opaTests.qunit.html (New)
<!DOCTYPE html>
<html>
<head>
<title>Opa tests for SAPUI5 Walkthrough</title>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<meta charset="utf-8">
<script id="sap-ui-bootstrap"
src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-resourceroots='{
"sap.ui.demo.walkthrough.test.integration": "./",
"sap.ui.demo.app.test" : "../mockServer"
}'>
</script>
<script>
jQuery.sap.require("sap.ui.qunit.qunit-css");
jQuery.sap.require("sap.ui.thirdparty.qunit");
jQuery.sap.require("sap.ui.qunit.qunit-junit");
jQuery.sap.require("sap.ui.test.opaQunit");
jQuery.sap.require("sap.ui.test.Opa5");
// pages
jQuery.sap.require("sap.ui.demo.walkthrough.test.integration.pages.App");
// journeys
jQuery.sap.require("sap.ui.demo.walkthrough.test.integration.navigationJourney");
</script>
</head>
<body>
<div id="content"></div>
<div id="qunit"></div>
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
169
<div id="qunit-fixture"></div>
</body>
</html>
This file contains our test suite for all OPA tests of the app. We define a namespace to the current folder for our
integration tests and to the mockServer page so that we can easily start the app by this namespace instead of
giving the file name directly in each test.
Then we load the basic QUnit and OPA functionality from OpenUI5 and our custom page object so that we can
execute the test journey. The navigationJourney we defined above is also loaded and the test functions inside
are immediately executed.
When you call the webapp/test/integration/opaTests.qunit.html page of your project on the server, you
should see the QUnit layout and a test “Should see the hello dialog” is executed immediately. It will load the app in
a small iFrame in the lower right of the page. There you can see what operations the test is performing on the app,
if everything works correctly the button click is triggered, then a dialog is shown and the test case is green.
Conventions
● OPA tests are located in the webapp/test/integration folder of the application.
● Use page objects and journeys for structuring OPA tests.
Related Information
Integration Testing with One Page Acceptance Tests (OPA5) [page 871]
Samples: sap.ui.test.Opa5
Step 30: Debugging Tools
Even though we have added a basic test coverage in the previous steps, it seems like we accidentally broke our
app, because it does not display prices to our invoices anymore. We need to debug the issue and fix it before
someone finds out.
Luckily, OpenUI5 provides a couple of debugging tools that we can use within the app to check the application
logic and the developer tools of modern browsers are also quite good. We will now check for the root cause.
170
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Preview
Figure 38: The diagnostics window
Coding
You can view and download all files at Walkthrough - Step 30.
webapp/view/InvoiceList.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.InvoiceList"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<List
id="invoiceList"
class="sapUiResponsiveMargin"
width="auto"
items="{
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
171
path : 'invoice>/Invoices',
sorter : {
path : 'ShipperName',
group : true
}
}">
<headerToolbar>
<Toolbar>
<Title text="{i18n>invoiceListTitle}"/>
<ToolbarSpacer/>
<SearchField width="50%" search="onFilterInvoices"/>
</Toolbar>
</headerToolbar>
<items>
<ObjectListItem
title="{invoice>Quantity} x {invoice>ProductName}"
number="{
parts: [{path: 'invoice>ExTendedPrice'}, {path: 'view>/
currency'}],
type: 'sap.ui.model.type.Currency',
formatOptions: {
showMeasure: false
}
}"
numberUnit="{view>/currency}"
numberState="{=
${invoice>ExtendedPrice} > 50 ? 'Error' :
'Success' }">
<attributes>
<ObjectAttribute text="{
path: 'invoice>Status',
formatter: '.formatter.statusText'
}"/>
</attributes>
</ObjectListItem>
</items>
</List>
</mvc:View>
We introduced a typo in the binding of the number attribute to simulate a frequent error; instead of using
'invoice>ExtendedPrice' we use 'invoice>ExTendedPrice'. Now we call the app and notice that the
price is actually missing. By pressing CTRL + ALT + SHIFT + S we open the OpenUI5 support diagnostics
tool and check the app.
Note
If you use the Google Chrome browser, you can install the UI5 Inspector plugin. With this plugin, you can easily
debug your - or OpenUI5-based apps. For more information, see UI5 Inspector [page 974].
Besides technical information about the app and a trace that is similar to the developer tools console of the
browser, there is a really handy tool for checking such errors in this dialog. Open the tab Control Tree by clicking on
the expand symbol on the right.
A hierarchical tree of OpenUI5 controls is shown on the left and the properties of the selected control are displayed
on the right. If we now select the first ObjectListItem control of the tree and go to the Binding Infos tab on the
right, we can actually see that the binding path of the number attribute is marked as invalid. We can now correct
the error in the view and the price should appear in the list of invoices again.
Sometimes errors are not as easy to spot and you actually need to debug the JavaScript code with the tools of the
browser. For performance reasons, the OpenUI5 files are shipped in a minified version, this means that all possible
variable names are shortened and comments are removed.
172
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
This makes debugging harder because the code is a lot less readable. You can load the debug sources by adding
the URL parameter sap-ui-debug=true or by pressing CTRL + ALT + SHIFT + P and select Use Debug
Sources in the dialog box that is displayed. After reloading the page, you can see in the Network tab of the
browser’s developer tools that now a lot of files with the –dbg suffix are loaded. These are the source code files
that include comments and the uncompressed code of the app and the OpenUI5 artifacts.
Figure 39: Technical information dialog
For a more detailed explanation of the OpenUI5 support tools, go through the Troubleshooting [page 205] tutorial.
If you're stuck and need help for some development task, you can also post a question in the OpenUI5-related
forums, for example in the SAP Community or on Stack Overflow.
Conventions
● As per OpenUI5 convention uncompressed source files end with *-dbg.js
Related Information
Debugging [page 926]
Diagnostics [page 936]
Technical Information Dialog [page 932]
Step 31: Routing and Navigation
So far, we have put all app content on one single page. As we add more and more features, we want to split the
content and put it on separate pages.
In this step, we will use the OpenUI5 navigation features to load and show a separate detail page that we can later
use to display details for an invoice. In the previous steps, we defined the page directly in the app view so that it is
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
173
displayed when the app is loaded. We will now use the OpenUI5 router class to load the pages and update the URL
for us automatically. We specify a routing configuration for our app and create a separate view for each page of the
app, then we connect the views by triggering navigation events.
Preview
Figure 40: A second page is added to display the invoice
Coding
You can view and download all files at Walkthrough - Step 31.
webapp/manifest.json
{
174
"_version": "1.8.0",
…
"sap.ui5": {
…
"models": {
…
},
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "sap.ui.demo.walkthrough.view",
"controlId": "app",
"controlAggregation": "pages",
"async": true
},
"routes": [
{
"pattern": "",
"name": "overview",
"target": "overview"
},
{
"pattern": "detail",
"name": "detail",
"target": "detail"
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
}
}
}
}
],
"targets": {
"overview": {
"viewID": "overview"
"viewName": "Overview"
},
"detail": {
"viewId": "detail"
"viewName": "Detail"
}
}
We add a new “routing" section to the sap.ui5 part of the descriptor. There are three subsections that define the
routing and navigation structure of the app:
● config
This section contains the global router configuration and default values that apply for all routes and targets.
We define the router class that we want to use and where our views are located in the app. To load and display
views automatically, we also specify which control is used to display the pages and what aggregation should be
filled when a new page is displayed.
● routes
Each route defines a name, a pattern, and one or more targets to navigate to when the route has been hit. The
pattern is basically the URL part that matches to the route, we define two routes for our app. The first one is a
default route that will show the overview page with the content from the previous steps, and the second is the
detail route with the URL pattern detail that will show a new page.
● targets
A target defines a view that is displayed, it is associated with one or more routes and it can also be displayed
manually from within the app. Whenever a target is displayed, the corresponding view is loaded and shown in
the app. In our app we simply define two targets with a view name that corresponds to the target name.
webapp/Component.js
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/model/json/JSONModel",
"sap/ui/demo/walkthrough/controller/HelloDialog"
], function (UIComponent, JSONModel, HelloDialog) {
"use strict";
return UIComponent.extend("sap.ui.demo.walkthrough.Component", {
metadata: {
manifest: "json"
},
init: function () {
…
// set dialog
this._helloDialog = new HelloDialog(this.getRootControl());
// create the views based on the url/hash
this.getRouter().initialize();
},
});
exit : function() {
this._helloDialog.destroy();
delete this._helloDialog;
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
175
});
});
},
openHelloDialog : function () {
this._helloDialog.open();
}
In the component initialization method, we now add a call to initialize the router. We do not need to instantiate the
router manually, it is automatically instantiated based on our AppDescriptor configuration and assigned to the
component.
Initializing the router will evaluate the current URL and load the corresponding view automatically. This is done
with the help of the routes and targets that have been configured in the AppDescriptor. If a route has been hit,
the view of its corresponding target is loaded and displayed.
webapp/view/Overview.view.xml (New)
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.App"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Page title="{i18n>homePageTitle}">
<headerContent>
<Button
icon="sap-icon://hello-world"
press="onOpenDialog"/>
</headerContent>
<content>
<mvc:XMLView viewName="sap.ui.demo.walkthrough.view.HelloPanel"/>
<mvc:XMLView viewName="sap.ui.demo.walkthrough.view.InvoiceList"/>
</content>
</Page>
</mvc:View>
We move the content of the previous steps from the App view to a new Overview view. For simplicity, we do not
change the controller as it only contains our helper method to open the dialog, that means we reuse the controller
sap.ui.demo.walkthrough.controller.App for two different views (for the new overview and for the app
view). However, two instances of that controller are instantiated at runtime. In general, one instance of a controller
is instantiated for each view that references the controller.
webapp/view/App.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.App"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
displayBlock="true">
<App class="myAppDemoWT" id="app"/>
</mvc:View>
Our App view is now only containing the empty app tag. The router will automatically add the view that
corresponds to the current URL into the app control. The router identifies the app control with the ID that
corresponds to the property controlId: “app” in the AppDescriptor.
176
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/view/Detail.view.xml (New)
<mvc:View
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Page
title="{i18n>detailPageTitle}">
<ObjectHeader
title="Invoice"/>
</Page>
</mvc:View>
Now we add a second view for the detail view. It only contains a page and an ObjectHeader control that displays
the static text Invoice for now.
webapp/i18n/i18n.properties
…
# Invoice List
invoiceListTitle=Invoices
invoiceStatusA=New
invoiceStatusB=In Progress
invoiceStatusC=Done
# Detail Page
detailPageTitle=Walkthrough - Details
We add a new string to the resource bundle for the detail page title.
webapp/view/InvoiceList.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.InvoiceList"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<List
…>
…
<items>
<ObjectListItem
title="{invoice>Quantity} x {invoice>ProductName}"
number="{
parts: [{path: 'invoice>ExtendedPrice'}, {path: 'view>/
currency'}],
}"
'Success' }"
type: 'sap.ui.model.type.Currency',
formatOptions: {
showMeasure: false
}
numberUnit="{view>/currency}"
numberState="{=
${invoice>ExtendedPrice} > 50 ? 'Error' :
type="Navigation"
press="onPress">
<firstStatus>
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
177
<ObjectStatus text="{
path: 'invoice>Status',
formatter: '.formatter.statusText'
}"/>
</firstStatus>
</ObjectListItem>
</items>
</List>
</mvc:View>
In the invoice list view we add a press event to the list item and set the item type to Navigation so that the item
can actually be clicked.
webapp/controller/InvoiceList.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel",
"sap/ui/demo/walkthrough/model/formatter",
"sap/ui/model/Filter",
"sap/ui/model/FilterOperator"
], function (Controller, JSONModel, formatter, Filter, FilterOperator) {
"use strict";
return Controller.extend("sap.ui.demo.walkthrough.controller.InvoiceList", {
…
onPress: function (oEvent) {
var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
oRouter.navTo("detail");
}
});
});
We add the event handler function to the controller of our invoices list. Now it is time to navigate to the detail page
by clicking an item in the invoice list. We access the router instance for our app by calling the helper method
sap.ui.core.UIComponent.getRouterFor(this). On the router we call the navTo method to navigate to the
detail route that we specified in the routing configuration.
You should now see the detail page when you click an item in the list of invoices.
Conventions
● Define the routing configuration in the descriptor
Related Information
Routing and Navigation [page 797]
Tutorial: Navigation and Routing [page 269]
API Reference: sap.m.routing.Router
Samples: sap.m.routing.Router
178
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Step 32: Routing with Parameters
We can now navigate between the overview and the detail page, but the actual item that we selected in the
overview is not displayed on the detail page yet. A typical use case for our app is to show additional information for
the selected item on the detail page.
To make this work, we have to pass over the information which item has been selected to the detail page and show
the details for the item there.
Preview
Figure 41: The selected invoice details are now shown in the details page
Coding
You can view and download all files at Walkthrough - Step 32.
webapp/manifest.json
{
"_version": "1.8.0",
…
"sap.ui5": {
…
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "sap.ui.demo.walkthrough.view",
"controlId": "app",
"controlAggregation": "pages",
"async": true
},
"routes": [
{
"pattern": "",
"name": "overview",
"target": "overview"
},
{
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
179
}
],
}
}
}
}
"pattern": "detail/{invoicePath}",
"name": "detail",
"target": "detail"
"targets": {
"overview": {
"viewID": "overview"
"viewName": "Overview"
},
"detail": {
"viewId": "detail"
"viewName": "Detail"
}
We now add a navigation parameter invoicePath to the detail route so that we can hand over the information for
the selected item to the detail page. Mandatory navigation parameters are defined with curly brackets.
webapp/view/Detail.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.Detail"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Page
title="{i18n>detailPageTitle}">
<ObjectHeader
intro="{invoice>ShipperName}"
title="{invoice>ProductName}"/>
</Page>
</mvc:View>
We add a controller that will take care of setting the item's context on the view and bind some properties of the
ObjectHeader to the fields of our invoice model. We could add more detailed information from the invoice
object here, but for simplicity reasons we just display two fields for now.
webapp/controller/InvoiceList.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel",
"sap/ui/demo/walkthrough/model/formatter",
"sap/ui/model/Filter",
"sap/ui/model/FilterOperator"
], function (Controller, JSONModel, formatter, Filter, FilterOperator) {
"use strict";
return Controller.extend("sap.ui.demo.walkthrough.controller.InvoiceList", {
…
onPress: function (oEvent) {
var oItem = oEvent.getSource();
var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
180
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
});
});
}
oRouter.navTo("detail", {
invoicePath: oItem.getBindingContext("invoice").getPath().substr(1)
});
The control instance that has been interacted with can be accessed by the getSource method that is available for
all OpenUI5 events. It will return the ObjectListItem that has been clicked in our case. We will use it to pass the
information of the clicked item to the detail page so that the same item can be displayed there.
In the navTo method we now add a configuration object to fill the navigation parameter invoicePath with the
current information of the item. This will update the URL and navigate to the detail view at the same time. On the
detail page, we can access this context information again and display the corresponding item.
To identify the object that we selected, we would typically use the key of the item in the back-end system because
it is short and precise. For our invoice items however, we do not have a simple key and directly use the binding path
to keep the example short and simple. The path to the item is part of the binding context which is a helper object of
OpenUI5 to manage the binding information for controls. The binding context can be accessed by calling the
getBindingContext method with the model name on any bound OpenUI5 control. We need to remove the first /
from the binding path by calling .substr(1) on the string because this is a special character in URLs and is not
allowed, we will add it again on the detail page.
webapp/controller/Detail.controller.js (New)
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function (Controller) {
"use strict";
return Controller.extend("sap.ui.demo.walkthrough.controller.Detail", {
onInit: function () {
var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
oRouter.getRoute("detail").attachPatternMatched(this._onObjectMatched,
this);
},
_onObjectMatched: function (oEvent) {
this.getView().bindElement({
path: "/" + oEvent.getParameter("arguments").invoicePath,
model: "invoice"
});
}
});
});
Our last piece to fit the puzzle together is the detail controller. It needs to set the context that we passed in with the
URL parameter invoicePath on the view, so that the item that has been selected in the list of invoices is actually
displayed, otherwise, the view would simply stay empty.
In the init method of the controller we fetch the instance of our app router and attach to the detail route by
calling the method attachPatternMatched on the route that we accessed by its name. We register an internal
callback function _onObjectMatched that will be executed when the route is hit, either by clicking on the item or
by calling the app with a URL for the detail page.
In the _onObjectMatched method that is triggered by the router we receive an event that we can use to access
the URL and navigation parameters. The arguments parameter will return an object that corresponds to our
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
181
navigation parameters from the route pattern. We access the invoicePath that we set in the invoice list
controller and call the bindElement function on the view to set the context. We have to add the root / in front of
the path again that was removed for passing on the path as a URL parameter.
The bindElement function is creating a binding context for a OpenUI5 control and receives the model name as
well as the path to an item in a configuration object. This will trigger an update of the UI controls that we connected
with fields of the invoice model. You should now see the invoice details on a separate page when you click on an
item in the list of invoices.
Conventions
● Define the routing configuration in the AppDescriptor
Related Information
Routing and Navigation [page 797]
Tutorial: Navigation and Routing [page 269]
API Reference: sap.m.routing.Router
Samples: sap.m.routing.Router
Step 33: Routing Back and History
Now we can navigate to our detail page and display an invoice, but we cannot go back to the overview page yet.
We'll add a back button to the detail page and implement a function that shows our overview page again.
Preview
Figure 42: A back button is now displayed on the detail page
Coding
You can view and download all files at Walkthrough - Step 33.
182
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/view/Detail.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.Detail"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Page
title="{i18n>detailPageTitle}"
showNavButton="true"
navButtonPress="onNavBack">
<ObjectHeader
intro="{invoice>ShipperName}"
title="{invoice>ProductName}"/>
</Page>
</mvc:View>
On the detail page, we tell the control to display a back button by setting the parameter showNavButton to true
and register an event handler that is called when the back button is pressed.
webapp/controller/Detail.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/core/routing/History"
], function (Controller, History) {
"use strict";
return Controller.extend("sap.ui.demo.walkthrough.controller.Detail", {
onInit: function () {
var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
oRouter.getRoute("detail").attachPatternMatched(this._onObjectMatched,
this);
},
_onObjectMatched: function (oEvent) {
this.getView().bindElement({
path: "/" + oEvent.getParameter("arguments").invoicePath,
model: "invoice"
});
},
onNavBack: function () {
var oHistory = History.getInstance();
var sPreviousHash = oHistory.getPreviousHash();
});
});
}
if (sPreviousHash !== undefined) {
window.history.go(-1);
} else {
var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
oRouter.navTo("overview", {}, true);
}
We load a new dependency that helps us to manage the navigation history from the sap.ui.core.routing
namespace and add the implementation for the event handler to our detail page controller.
In the event handler we access the navigation history and try to determine the previous hash. In contrast to the
browser history, we will get a valid result only if a navigation step inside our app has already happened. Then we will
simply use the browser history to go back to the previous page. If no navigation has happened before, we can tell
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
183
the router to go to our overview page directly. The third parameter true tells the router to replace the current
history state with the new one since we actually do a back navigation by ourselves. The second parameter is an
empty array ({}) as we do not pass any additional parameters to this route.
This implementation is a bit better than the browser’s back button for our use case. The browser would simply go
back one step in the history even though we were on another page outside of the app. In the app, we always want
to go back to the overview page even if we came from another link or opened the detail page directly with a
bookmark. You can try it by loading the detail page in a new tab directly and clicking on the back button in the app,
it will still go back to the overview page.
Conventions
● Add a path to go back to the parent page when the history state is unclear.
Step 34: Custom Controls
In this step, we are going to extend the functionality of OpenUI5 with a custom control. We want to rate the product
shown on the detail page, so we create a composition of multiple standard controls using the OpenUI5 extension
mechanism and add some glue code to make them work nicely together. This way, we can reuse the control across
the app and keep all related functionality in one module.
Preview
Figure 43: A custom product rating control is added to the detail page
Coding
You can view and download all files at Walkthrough - Step 34.
184
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/control/ProductRating.js (New)
sap.ui.define([
"sap/ui/core/Control"
], function (Control) {
"use strict";
return Control.extend("sap.ui.demo.walkthrough.control.ProductRating", {
metadata : {
},
init : function () {
},
renderer : function (oRM, oControl) {
}
});
});
We create a new folder control and a file ProductRating.js that will hold our new control. As with our
controllers and views, the custom control inherits the common control functionality from a OpenUI5 base object,
for controls this is done by extending the base class sap.ui.core.Control.
Custom controls are small reuse components that can be created within the app very easily. Due to their nature,
they are sometimes also referred to as "notepad” or “on the fly” controls. A custom control is a JavaScript object
that has two special sections (metadata and renderer) and a number of methods that implement the
functionality of the control.
The metadata section defines the data structure and thus the API of the control. With this meta information on
the properties, events, and aggregations of the control OpenUI5 automatically creates setter and getter methods
and other convenience functions that can be called within the app.
The renderer defines the HTML structure that will be added to the DOM tree of your app whenever the control is
instantiated in a view. It is usually called initially by the core of OpenUI5 and whenever a property of the control is
changed. The parameter oRM of the render function is the OpenUI5 render manager that can be used to write
strings and control properties to the HTML page.
The init method is a special function that is called by the OpenUI5 core whenever the control is instantiated. It
can be used to set up the control and prepare its content for display.
Note
Controls always extend sap.ui.core.Control and render themselves. You could also extend
sap.ui.core.Element or sap.ui.base.ManagedObject directly if you want to reuse life cycle features of
OpenUI5 including data binding for objects that are not rendered. Please refer to the API reference to learn
more about the inheritance hierarchy of controls.
webapp/control/ProductRating.js
sap.ui.define([
"sap/ui/core/Control",
"sap/m/RatingIndicator",
"sap/m/Label",
"sap/m/Button"
], function (Control, RatingIndicator, Label, Button) {
"use strict";
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
185
return Control.extend("sap.ui.demo.walkthrough.control.ProductRating", {
metadata : {
properties : {
value:
{type : "float", defaultValue : 0}
},
aggregations : {
_rating : {type : "sap.m.RatingIndicator", multiple: false,
visibility : "hidden"},
_label : {type : "sap.m.Label", multiple: false, visibility :
"hidden"},
_button : {type : "sap.m.Button", multiple: false, visibility :
"hidden"}
},
events : {
change : {
parameters : {
value : {type : "int"}
}
}
}
},
init : function () {
this.setAggregation("_rating", new RatingIndicator({
value: this.getValue(),
iconSize: "2rem",
visualMode: "Half",
liveChange: this._onRate.bind(this)
}));
this.setAggregation("_label", new Label({
text: "{i18n>productRatingLabelInitial}"
}).addStyleClass("sapUiSmallMargin"));
this.setAggregation("_button", new Button({
text: "{i18n>productRatingButton}",
press: this._onSubmit.bind(this)
}).addStyleClass("sapUiTinyMarginTopBottom"));
},
setValue: function (fValue) {
this.setProperty("value", fValue, true);
this.getAggregation("_rating").setValue(fValue);
},
reset: function () {
var oResourceBundle = this.getModel("i18n").getResourceBundle();
this.setValue(0);
this.getAggregation("_label").setDesign("Standard");
this.getAggregation("_rating").setEnabled(true);
this.getAggregation("_label").setText(oResourceBundle.getText("productRatingLabelIni
tial"));
this.getAggregation("_button").setEnabled(true);
},
_onRate : function (oEvent) {
var oRessourceBundle = this.getModel("i18n").getResourceBundle();
var fValue = oEvent.getParameter("value");
this.setProperty("value", fValue, true);
this.getAggregation("_label").setText(oRessourceBundle.getText("productRatingLabelIn
dicator", [fValue, oEvent.getSource().getMaxValue()]));
this.getAggregation("_label").setDesign("Bold");
},
_onSubmit : function (oEvent) {
var oResourceBundle = this.getModel("i18n").getResourceBundle();
186
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
this.getAggregation("_rating").setEnabled(false);
this.getAggregation("_label").setText(oResourceBundle.getText("productRatingLabelFin
al"));
this.getAggregation("_button").setEnabled(false);
this.fireEvent("change", {
value: this.getValue()
});
},
renderer : function (oRM, oControl) {
oRM.write("<div");
oRM.writeControlData(oControl);
oRM.addClass("myAppDemoWTProductRating");
oRM.writeClasses();
oRM.write(">");
oRM.renderControl(oControl.getAggregation("_rating"));
oRM.renderControl(oControl.getAggregation("_label"));
oRM.renderControl(oControl.getAggregation("_button"));
oRM.write("</div>");
}
});
});
We now enhance our new custom control with the custom functionality that we need. In our case we want to create
an interactive product rating, so we define a value and use three internal controls that are displayed updated by
our control automatically. A RatingIndicator control is used to collect user input on the product, a label is
displaying further information, and a button submits the rating to the app to store it.
In the metadata section we therefore define several properties that we make use in the implementation:
● Properties
○ Value
We define a control property value that will hold the value that the user selected in the rating. Getter and
setter function for this property will automatically be created and we can also bind it to a field of the data
model in the XML view if we like.
● Aggregations
As described in the first paragraph, we need three internal controls to realize our rating functionality. We
therefore create three “hidden aggregations” by setting the visibility attribute to hidden. This way, we
can use the models that are set on the view also in the inner controls and OpenUI5 will take care of the lifecycle
management and destroy the controls when they are not needed anymore. Aggregations can also be used to
hold arrays of controls but we just want a single control in each of the aggregations so we need to adjust the
cardinality by setting the attribute multiple to false.
○ _rating: A sap.m.RatingIndicator control for user input
○ _label: A sap.m.Label to display additional information
○ _button: A sap.m.Button to submit the rating
Note
You can define aggregations and associations for controls. The difference is in the relation between
the parent and the related control:
○ An aggregation is a strong relation that also manages the lifecycle of the related control, for example,
when the parent is destroyed, the related control is also destroyed. Also, a control can only be assigned
to one single aggregation, if it is assigned to a second aggregation, it is removed from the previous
aggregation automatically.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
187
○ An association is a weak relation that does not manage the lifecycle and can be defined multiple times.
To have a clear distinction, an association only stores the ID, whereas an aggregation stores the direct
reference to the control. We do not specify associations in this example, as we want to have our internal
controls managed by the parent.
● Events
○ Change
We specify a change event that the control will fire when the rating is submitted. It contains the current
value as an event parameter. Applications can register to this event and process the result similar to
“regular” OpenUI5 controls, which are in fact built similar to custom controls.
In the init function that is called by OpenUI5 automatically whenever a new instance of the control is
instantiated, we set up our internal controls. We instantiate the three controls and store them in the internal
aggregation by calling the framework method setAggregation that has been inherited from
sap.ui.core.Control. We pass on the name of the internal aggregations that we specified above and the new
control instances. We specify some control properties to make our custom control look nicer and register a
liveChange event to the rating and a press event to the button. The initial texts for the label and the button are
referenced from our i18n model.
Let’s ignore the other internal helper functions and event handlers for now and define our renderer. With the help
of the OpenUI5 render manager and the control instance that are passed on as a reference, we can now render the
HTML structure of our control. We render the start of the outer <div> tag as <div and call the helper method
writeControlData to render the ID and other basic attributes of the control inside the div tag. Next, we add a
custom CSS class so that we can define styling rules for the custom control in our CSS file later. This CSS class
and others that have been added in the view are then rendered by calling writeClasses on the renderer instance.
Then we close the surrounding div tag and render three internal controls by passing the content of the internal
aggregation to the render managers renderControl function. This will call the renderer of the controls and add
their HTML to the page. Finally, we close our surrounding <div> tag.
The setValue is an overridden setter. OpenUI5 will generate a setter that updates the property value when called
in a controller or defined in the XML view, but we also need to update the internal rating control in the hidden
aggregation to reflect the state properly. Also, we can skip the rerendering of OpenUI5 that is usually triggered
when a property is changed on a control by calling the setProperty method to update the control property with
true as the third parameter.
Now we define the event handler for the internal rating control. It is called every time the user changes the rating.
The current value of the rating control can be read from the event parameter value of the
sap.m.RatingIndicator control. With the value we call our overridden setter to update the control state, then
we update the label next to the rating to show the user which value he has selected currently and also displays
the maximum value. The string with the placeholder values is read from the i18n model that is assigned to the
control automatically.
Next, we have the press handler for the rating button that submits our rating. We assume that rating a product is
a one-time action and first disable the rating and the button so that the user is not allowed to submit another
rating. We also update the label to show a "Thank you for your rating!" message, then we fire the change event of
the control and pass in the current value as a parameter so that applications that are listening to this event can
react on the rating interaction.
We define the reset method to be able to revert the state of the control on the UI to its initial state so that the user
can again submit a rating.
188
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/view/Detail.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.Detail"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
xmlns:wt="sap.ui.demo.walkthrough.control">
<Page
title="{i18n>detailPageTitle}"
showNavButton="true"
navButtonPress="onNavBack">
<ObjectHeader
intro="{invoice>ShipperName}"
title="{invoice>ProductName}"/>
<wt:ProductRating id="rating" class="sapUiSmallMarginBeginEnd"
change="onRatingChange"/>
</Page>
</mvc:View>
A new namespace wt is defined on the detail view so that we can reference our custom controls easily in the view.
We then add an instance of the ProductRating control to our detail page and register an event handler for the
change event. To have a proper layout, we also add a margin style class.
webapp/controller/Detail.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/core/routing/History",
"sap/m/MessageToast"
], function (Controller, History, MessageToast) {
use strict;
return Controller.extend(sap.ui.demo.walkthrough.controller.Detail, {
…
_onObjectMatched: function (oEvent) {
this.byId("rating").reset();
this.getView().bindElement({
path: "/" + oEvent.getParameter("arguments").invoicePath,
model: "invoice"
});
},
onNavBack: function () {
…
},
onRatingChange : function (oEvent) {
var fValue = oEvent.getParameter("value");
var oResourceBundle =
this.getView().getModel("i18n").getResourceBundle();
MessageToast.show(oResourceBundle.getText("ratingConfirmation",
[fValue]));
}
});
});
In the Detail controller we load the dependency to the sap.m.MessageToast because we will simply display a
message instead of sending the rating to the backend to keep the example simple. The event handler
onRatingChange reads the value of our custom change event that is fired when the rating has been submitted.
We then display a confirmation message with the value in a MessageToast control.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
189
In the onObjectMatched private method, we call the reset method to make it possible to submit another rating
as soon as the detail view is displayed for a different item.
webapp/css/style.css
.myAppDemoWTmyCustomButton.sapMBtn {
margin-right: 0.125rem;
}
.myAppDemoWTmyCustomText {
font-weight: bold;
}
/* ProductRating */
.myAppDemoWTProductRating {
padding: 0.75rem;
}
.myAppDemoWTProductRating .sapMRI {
vertical-align: initial;
}
To layout our control, we add a little padding to the root class to have some space around the three inner controls,
and we override the alignment of the RatingIndicator control so that it is aligned in one line with the label and
the button.
We could also do this with more HTML in the renderer but this is the simplest way and it will only be applied inside
our custom control. However, please be aware that the custom control is in your app and might have to be adjusted
when the inner controls change in future versions of OpenUI5.
webapp/i18n/i18n.properties
…
# Detail Page
detailPageTitle=Walkthrough - Details
ratingConfirmation=You have rated this product with {0} stars
# Product Rating
productRatingLabelInitial=Please rate this product
productRatingLabelIndicator=Your rating: {0} out of {1}
productRatingLabelFinal=Thank you for your rating!
productRatingButton=Rate
The resource bundle is extended with the confirmation message and the strings that we reference inside the
custom control. We can now rate a product on the detail page with our brand new control.
Conventions
● Put custom controls in the control folder of your app.
190
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Related Information
Developing OpenUI5 Controls [page 1138]
Defining the Control Metadata [page 1141]
API Reference: sap.m.RatingIndicator
Samples: sap.m.RatingIndicator
API Reference: sap.m.Label
Samples: sap.m.Label
API Reference: sap.m.Button
Samples: sap.m.Button
API Reference: sap.ui.core.Control
API Reference: sap.ui.core.Element
API Reference: sap.ui.base.ManagedObject
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
191
Step 35: Responsiveness
In this step, we improve the responsiveness of our app. OpenUI5 applications can be run on phone, tablet, and
desktop devices and we can configure the application to make best use of the screen estate for each scenario.
Fortunately, OpenUI5 controls like the sap.m.Table already deliver a lot of features that we can use.
Preview
Figure 44: A responsive table is hiding some of the columns on small devices
192
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Coding
You can view and download all files at Walkthrough - Step 35.
webapp/view/InvoiceList.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.InvoiceList"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Table
id="invoiceList"
class="sapUiResponsiveMargin"
width="auto"
items="{
path : 'invoice>/Invoices',
sorter : {
path : 'ShipperName',
group : true
}
}">
<headerToolbar>
<Toolbar>
<Title text="{i18n>invoiceListTitle}"/>
<ToolbarSpacer/>
<SearchField width="50%" search="onFilterInvoices"/>
</Toolbar>
</headerToolbar>
<columns>
<Column
hAlign="End"
minScreenWidth="Small"
demandPopin="true"
width="4em">
<Text text="{i18n>columnQuantity}"/>
</Column>
<Column>
<Text text="{i18n>columnName}"/>
</Column>
<Column
minScreenWidth="Small"
demandPopin="true">
<Text text="{i18n>columnStatus}"/>
</Column>
<Column
minScreenWidth="Tablet"
demandPopin="false">
<Text text="{i18n>columnSupplier}"/>
</Column>
<Column
hAlign="End">
<Text text="{i18n>columnPrice}"/>
</Column>
</columns>
<items>
<ColumnListItem
type="Navigation"
press="onPress">
<cells>
<ObjectNumber number="{invoice>Quantity}" emphasized="false"/>
<ObjectIdentifier title="{invoice>ProductName}"/>
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
193
currency'}],
<Text text="{
path: 'invoice>Status',
formatter: '.formatter.statusText'
}"/>
<Text text="{invoice>ShipperName}"/>
<ObjectNumber
number="{
parts: [{path: 'invoice>ExtendedPrice'}, {path: 'view>/
'Success' }"/>
type: 'sap.ui.model.type.Currency',
formatOptions: {
showMeasure: false
}
}"
unit="{view>/currency}"
state="{= ${invoice>ExtendedPrice} > 50 ? 'Error' :
</cells>
</ColumnListItem>
</items>
</Table>
</mvc:View>
We exchange the list with a table simply by replacing the tag <List> with <Table>. The table has a built-in
responsiveness feature that allows us to make the app more flexible. The table and the list share the same set of
properties so we can simply reuse these and also the sorter.
Since a table has multiple cells in each row, we have to define columns for our table and name these according to
the data. We add five sap.m.Column controls to the column aggregation and configure each one a bit differently:
● Quantity
This column will contain a short number, so we set the alignment to End (which means "right" in LTR
languages) and the width to 4em which is long enough for the column description. As a description text we use
a sap.m.Text control that references a property of the resource bundle. We set the property
minScreenWidth to Small to indicate that this column is not so important on phones. We will tell the table to
display this column below the main column by setting the property demandPopin to true.
● Name
Our main column that has a pretty large width to show all the details. It will always be displayed.
● Status
The status is not so important, so we also display it below the name field on small screens by setting
minScreenWidth to small and demandPopin to true
● Supplier
We completely hide the Supplier column on phone devices by setting minScreenWidth to Tablet and
demandPopin to false.
● Price
This column is always visible as it contains our invoice price.
Instead of the ObjectListItem that we had before, we will now split the information onto the cells that match the
columns defined above. Therefore we change it to a ColumnListItem control with the same attributes, but now
with cells aggregation. Here we create five controls to display our data:
● Quantity
A simple sap.m.ObjectNumber control that is bound to our data field.
● Name
A sap.m.ObjectIdentifier controls that specifies the name.
● Status
194
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
A sap.m.Text control with the same formatter as before.
● Supplier
A simple sap.m.Text control.
● Price
An ObjectNumber control with the same formatter as the attributes number and numberUnit from the
previous steps.
Now we have defined our table responsively and can see the results when we decrease the browsers screen size.
The Supplier column is not shown on phone sizes and the two columns Quantity and Status will be shown below
the name.
webapp/i18n/i18n.properties
# App Descriptor
appTitle=Hello World
appDescription=A simple walkthrough app that explains the most important concepts
of OpenUI5
# Hello Panel
showHelloButtonText=Say Hello
helloMsg=Hello {0}
homePageTitle=Walkthrough
helloPanelTitle=Hello World
openDialogButtonText=Say Hello With Dialog
dialogCloseButtonText=Ok
# Invoice List
invoiceListTitle=Invoices
invoiceStatusA=New
invoiceStatusB=In Progress
invoiceStatusC=Done
columnQuantity=Quantity
columnName=Name
columnSupplier=Supplier
columnStatus=Status
columnPrice=Price
# Detail Page
detailPageTitle=Walkthrough - Details
ratingConfirmation=You have rated this product with {0} stars
# Product Rating
productRatingLabelInitial=Please rate this product
productRatingLabelIndicator=Your rating: {0} out of {1}
productRatingLabelFinal=Thank you!
productRatingButton=Rate
We add the column names and the attribute titles to our i18n file.
We can see the results when we decrease the browser's screen size or open the app on a small device.
Conventions
● Optimize your application for the different screen sizes of phone, tablet, and desktop devices.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
195
Related Information
Configuring Responsive Behavior of a Table [page 1253]
196
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Step 36: Device Adaptation
We now configure the visibility and properties of controls based on the device that we run the application on. By
making use of the sap.ui.Device API and defining a device model we will make the app look great on many
devices.
Preview
Figure 45: On phone devices, the panel is collapsed to save screen space and a button is hidden
Coding
You can view and download all files at Walkthrough - Step 36.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
197
webapp/view/HelloPanel.view.xml
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.HelloPanel"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Panel
headerText="{i18n>helloPanelTitle}"
class="sapUiResponsiveMargin"
width="auto"
expandable="{device>/system/phone}"
expanded="{= !${device>/system/phone} }">
<content>
<Button
icon="sap-icon://world"
text="{i18n>openDialogButtonText}"
press="onOpenDialog"
class="sapUiSmallMarginEnd sapUiVisibleOnlyOnDesktop"/>
<Button
text="{i18n>showHelloButtonText}"
press="onShowHello"
class="myCustomButton"/>
<Input
value="{/recipient/name}"
valueLiveUpdate="true"
width="60%"/>
<FormattedText
htmlText="Hello {/recipient/name}"
class="sapUiSmallMargin sapThemeHighlight-asColor myCustomText"/>
</content>
</Panel>
</mvc:View>
We add two new properties expandable and expanded to the HelloPanel. The user can now close and open the
panel to have more space for the table below on devices with small screens. The property expandable is bound to
a model named device and the path /system/phone. So the panel can be expanded on phone devices only. The
device model is filled with the sap.ui.Device API of OpenUI5 as we see further down. The expanded property
controls the state of the panel and we use expression binding syntax to close it on phone devices and have the
panel expanded on all other devices. The device API of OpenUI5 offers more functionality to detect various devicespecific settings, please have a look at the documentation for more details.
Note
The sap.ui.Device API detects the device type (Phone, Tablet, Desktop) based on the user agent and many
other properties of the device. Therefore simply reducing the screen size will not change the device type. To test
this feature, you will have to enable device emulation in your browser or open it on a real device.
We can also hide single controls by device type when we set a CSS class like sapUiVisibleOnlyOnDesktop or
sapUiHideOnDesktop . We only show the button that opens the dialog on desktop devices and hide it for other
devices. For more options, see the documentation linked below.
webapp/Component.js
sap.ui.define([
198
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
"sap/ui/core/UIComponent",
"sap/ui/model/json/JSONModel",
"sap/ui/demo/walkthrough/controller/HelloDialog",
"sap/ui/Device"
], function (UIComponent, JSONModel, HelloDialog, Device) {
"use strict";
return UIComponent.extend("sap.ui.demo.walkthrough.Component", {
metadata: {
manifest: "json"
},
init: function () {
// call the init function of the parent
UIComponent.prototype.init.apply(this, arguments);
// set data model
var oData = {
recipient: {
name: "World"
}
};
var oModel = new JSONModel(oData);
this.setModel(oModel);
// disable batch grouping for v2 API of the northwind service
this.getModel("invoice").setUseBatch(false);
// set device model
var oDeviceModel = new JSONModel(Device);
oDeviceModel.setDefaultBindingMode("OneWay");
this.setModel(oDeviceModel, "device");
// set dialog
this._helloDialog = new HelloDialog(this.getRootControl());
// create the views based on the url/hash
this.getRouter().initialize();
},
exit : function() {
this._helloDialog.destroy();
delete this._helloDialog;
},
openHelloDialog : function () {
this._helloDialog.open();
}
});
});
In the app component we add a dependency to sap.ui.Device and initialize the device model in the init
method. We can simply pass the loaded dependency Device to the constructor function of the JSONModel. This
will make most properties of the OpenUI5 device API available as a JSON model. The model is then set on the
component as a named model so that we can reference it in data binding as we have seen in the view above.
Note
We have to set the binding mode to OneWay as the device model is read-only and we want to avoid changing the
model accidentally when we bind properties of a control to it. By default, models in OpenUI5 are bidirectional
(TwoWay). When the property changes, the bound model value is updated as well.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
199
webapp/view/Detail.view.xml
Tip
You can test the device specific features of your app with the developer tools of your browser. For example in
Google Chrome, you can emulate a tablet or a phone easily and see the effects. Some responsive options of
OpenUI5 are only set initially when loading the app, so you might have to reload your page to see the results.
<mvc:View
controllerName="sap.ui.demo.walkthrough.controller.Detail"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
xmlns:wt="sap.ui.demo.walkthrough.control">
<Page
title="{i18n>detailPageTitle}"
showNavButton="true"
navButtonPress="onNavBack">
<ObjectHeader
responsive="true"
fullScreenOptimized="true"
number="{
parts: [{path: 'invoice>ExtendedPrice'}, {path: 'view>/currency'}],
type: 'sap.ui.model.type.Currency',
formatOptions: {
showMeasure: false
}
}"
numberUnit="{view>/currency}"
intro="{invoice>ShipperName}"
title="{invoice>ProductName}">
<attributes>
<ObjectAttribute title="{i18n>quantityTitle}"
text="{invoice>Quantity}"></ObjectAttribute>
<ObjectAttribute title="{i18n>dateTitle}" text="{
path: 'invoice>ShippedDate',
type: 'sap.ui.model.type.Date',
formatOptions: {
style: 'long',
source: {
pattern: 'yyyy-MM-ddTHH:mm:ss'
}
}
}"/>
</attributes>
</ObjectHeader>
<wt:ProductRating id="rating" class="sapUiSmallMarginBeginEnd"
change="onRatingChange"/>
</Page>
</mvc:View>
Some controls already have built-in responsive features that can be configured. The ObjectHeader control can be
put in a more flexible mode by setting the attribute responsive to true and fullScreenOptimized to true as
well. This will show the data that we add to the view now at different positions on the screen based on the device
size.
We add the number and numberUnit field from the list of the previous steps also to the ObjectHeader and use
the same formatter with the currency type as in the previous steps. We then define two attributes: The quantity
of the invoice and the shipped date which is part of the data model. We have not used this shippedDate field from
the invoices JSON file so far, it contains a date in typical string format.
200
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
We now use the Date type and provide the pattern of our date format in the source section of the format options. It
will display a more human-readable formatted date text that also fits to small screen devices.
webapp/controller/Detail.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/core/routing/History",
"sap/m/MessageToast",
"sap/ui/model/json/JSONModel"
], function (Controller, History, MessageToast, JSONModel) {
"use strict";
return Controller.extend("sap.ui.demo.walkthrough.controller.Detail", {
onInit : function () {
var oViewModel = new JSONModel({
currency: "EUR"
});
this.getView().setModel(oViewModel, "view");
var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
oRouter.getRoute("detail").attachPatternMatched(this._onObjectMatched,
this);
},
_onObjectMatched : …
});
In the Detail controller we simply add the view model with our currency definition to display the number
properly. It is the same code as in the InvoiceList controller file.
webapp/i18n/i18n.properties
# Detail Page
detailPageTitle=Walkthrough - Details
ratingConfirmation=You have rated this product with {0} stars
dateTitle=Order date
quantityTitle=Quantity
We add the column names and the attribute titles to our i18n file.
We can see the results when we decrease the browser's screen size or open the app on a small device.
Conventions
Optimize your application for the different screen sizes of phone, tablet, and desktop devices.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
201
Related Information
API Reference: sap.ui.Device.media.RANGESETS
API Reference: sap.ui.Device
Step 37: Content Density
In the last step of our Walkthrough tutorial, we adjust the content density based on the user’s device. OpenUI5
contains different content densities allowing you to display larger controls for touch-enabled devices and a smaller,
more compact design for devices that are operated by mouse. In our app, we will detect the device and adjust the
density accordingly.
Preview
Figure 46: The content density is compact on desktop devices and cozy on touch-enabled devices
Coding
You can view and download all files at Walkthrough - Step 37.
webapp/Component.js
...
202
init: function () {
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
...
...
});
},
});
getContentDensityClass : function() {
if (!this._sContentDensityClass) {
if (!sap.ui.Device.support.touch) {
this._sContentDensityClass = "sapUiSizeCompact";
} else {
this._sContentDensityClass = "sapUiSizeCozy";
}
}
return this._sContentDensityClass;
}
To prepare the content density feature we will also add a helper method getContentDensityClass. OpenUI5
controls can be displayed in multiple sizes, for example in a compact size that is optimized for desktop and nontouch devices, and in a cozy mode that is optimized for touch interaction. The controls look for a specific CSS
class in the HTML structure of the application to adjust their size.
This helper method queries the sap.ui.Device API directly for touch support of the client and returns the CSS
class sapUiSizeCompact if touch interaction is not supported and sapUiSizeCozy for all other cases. We will
use it throughout the application coding to set the proper content density CSS class.
webapp/controller/App.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function (Controller) {
"use strict";
return Controller.extend("sap.ui.demo.walkthrough.controller.App", {
onInit: function () {
this.getView().addStyleClass(this.getOwnerComponent().getContentDensityClass());
},
onOpenDialog: function () {
this.getOwnerComponent().openHelloDialog();
}
});
});
We add a method onInit on the app controller that is called when the app view is instantiated. There we query the
helper function that we defined on the app component to set the corresponding style class on the app view, All
controls inside the app view will now automatically adjust either to the compact or cozy size as defined by the
style.
webapp/controller/HelloDialog.js
sap.ui.define([
"sap/ui/base/ManagedObject"
], function (ManagedObject) {
"use strict";
return ManagedObject.extend("sap.ui.demo.walkthrough.controller.HelloDialog", {
constructor : function (oView) {
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
203
},
this._oView = oView;
exit : function () {
delete this._oView;
},
open : function () {
var oView = this._oView;
var oDialog = oView.byId("helloDialog");
// create dialog lazily
if (!oDialog) {
var oFragmentController = {
onCloseDialog : function () {
oDialog.close();
}
};
// create dialog via fragment factory
oDialog = sap.ui.xmlfragment(oView.getId(),
"sap.ui.demo.walkthrough.view.HelloDialog", oFragmentController);
// connect dialog to the root view of this component (models, lifecycle)
oView.addDependent(oDialog);
// forward compact/cozy style into dialog
jQuery.sap.syncStyleClass(oView.getController().getOwnerComponent().getContentDensit
yClass(), oView, oDialog);
}
oDialog.open();
}
});
});
The "Hello World" dialog is not part of the app view but opened in a special part of the DOM called "static area".
The content density class defined on the app view is not known to the dialog so we sync the style class of the app
with the dialog manually.
webapp/manifest.json
...
"sap.ui5": {
...
"dependencies": {
...
},
"contentDensities": {
"compact": true,
"cozy": true
}
}
In the contentDensities section of the sap.ui5 namespace, we specify the modes that the application
supports. Containers like the SAP Fiori launchpad allow switching the content density based on these settings.
As we have just enabled the app to run in both modes depending on the devices capabilities, we can set both to
true in the application descriptor.
This was the last step, you have successfully completed the Walkthrough!
204
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Summary
You should now be familiar with the major development paradigms and concepts of OpenUI5 and have created a
very simple first app. You are now ready to build a proper app based on what you've learned.
If you want to dive deeper into specific topics, you can use the other tutorials that show some of the aspects of this
Walkthrough and advanced topics in more detail.
Related Information
Content Densities [page 832]
API Reference: sap.ui.Device.media.RANGESETS
API Reference: sap.ui.Device
API Reference: jQuery.sap.syncStyleClass
Troubleshooting
In this tutorial, we will show you some tools that will help you if you run into problems with your OpenUI5 app.
We will introduce you to the browser developer tools and show you the various tools that OpenUI5 offers.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
205
For example, the OpenUI5 tools can help you with the following tasks:
● Inspect and debug apps
● Examine bugs and analyze errors
● Simulate UI changes
● Find out how to improve performance
To help you practice using the tools, we created an app with errors that we will use throughout the tutorial. You can
view and download the app in the Demo Kit at Troubleshooting.
Get Help
If you're stuck and need help with a development task, you can also post a question in the OpenUI5-related forums,
for example in the SAP Community or on Stack Overflow.
206
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Step 1: Browser Developer Tools
In this step, you will learn how to use your browser's developers tools to troubleshoot your OpenUI5 app.
Most modern web browsers contain some form of Developer Tools. They allow you to examine the details of the
current web page. You can also use them to debug JavaScript code, analyze network performance, live-edit DOM
elements, and much more. As an example, we will show you how to use the Developer Tools in Google Chrome.
Other browsers have similar capabilities, and you can easily adapt the examples shown here to these browsers.
Opening the Example App and the Developer Tools
1. Download the example app with errors from the Demo Kit at Troubleshooting and run the app.
Note
If you run the app within the Demo Kit frame, this step will not work as described. Open the app in a new tab
first with
.
2. Open the Developer Tools by pressing F12 .
Inspecting DOM Elements and CSS Styles in the Elements Tab
1. Activate the Inspect Element mode by pressing Ctrl + Shift + C .
2. Click the Do Something button in the app.
The DOM tree in the Elements tab highlights the button's DOM element. Depending on which part of the
button (icon or text) you clicked, different HTML tags are highlighted.
3. Search for the following line:
<button id="__xmlview0--myButton" data-sap-ui="__xmlview0--myButton" ariadescribedby="__text1"
class="sapMBtn sapMBtnBase sapMBtnInverted">
The Styles section in the panel on the right shows the active and overruled (striked-through) CSS styles for the
DOM element that is currently selected.
4. In the Styles section, switch to the Computed tab.
You can see that the margin of the button is set to 0px.
5. In the context menu of the element, choose Edit as HTML and add sapUiLargeMargin to the class section
of the button tag.
You can immediately see the effect on the web page.
The edited element should now look like this:
<button id="__xmlview0--myButton" data-sap-ui="__xmlview0--myButton" ariadescribedby="__text1"
class="sapMBtn sapMBtnBase sapUiLargeMargin sapMBtnInverted">
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
207
6. In the Styles section, switch again to the Computed tab.
You can see that the margin of the button is now set to 48px.
Analyzing Messages in the Console Tab
Interacting with the document
1. Switch to the Console tab and enter $("#myButton")
The console displays the DOM element structure of the button:
[prevObject: Q.fn.init(1), context: document, selector: "#myButton"]
2. Examine the button element by expanding the structure.
3. In the Console tab, enter myView=sap.ui.getCore().byId("HeapOfShards---app").
4. In the Console tab, enter myView.byId("myButton").
The console displays the OpenUI5 structure of the button control:
f {bAllowTextSelection: true, mEventRegistry: Object, sId: "__xmlview0-myButton", mProperties: d, mAggregations: Object…}
Examine the OpenUI5 structure by expanding it.
Note
The method of retrieval is different for OpenUI5 controls and DOM elements:
OpenUI5 Control
DOM Element
myView.byId("myButton")
$("#myButton")
Watching messages
1. Switch to the Console tab.
2. Click the Do Something button in the app.
A MessageToast appears with the text Sorry, an error occurred!.
3. In the console, you see the following error message:
TypeError: evt.getSourceXYZ is not a function
at f.onPress (http://.../App.controller.js?eval:19:18)
...
This means that an error occurred in the onPress function in the App.controller.js file at line 19.
4. Click the first link in the stack trace after f.onPress to look at the source code where you can see that it
wasn't the generic getSource function that was called, but an undefined getSourceXYZ.
sMessage=evt.getSourceXYZ().getId() + " Pressed";
208
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Debugging in the Sources Tab
1. Switch to the Source tab.
2. To view the source of the App.controller file, press Ctrl + P , enter App.controller, and select
App.controller.js?eval.
3. Set a breakpoint in line 19 by clicking on the line number before the following line:
sMessage=evt.getSourceXYZ().getId() + " Pressed";
4. Click the Do Something button in the app.
The debugger stops at line 19.
5. In line 19, replace getSourceXYZ() with getSource() and press Ctrl + S :
sMessage=evt.getSourceXYZ().getId() + " Pressed";
6. Resume the execution of the code by pressing F8 .
The message toast is now displayed on the web page with the following message: o __xmlview0--myButton
Pressed
Checking the Network Tab
The Network tab shows the sequence and duration of files being loaded. It can be used to optimize loading
performance and debug request issues.
1. Switch to the Network tab.
2. Press F5 to reload the page.
You see a list of the files that are currently loaded.
You can see that the NavigationBar.js file is loaded, but the view does not contain any NavigationBar
elements so it is not used.
3. You can't edit the code directly in this tab. You have to fix the source files in your development environment
and then reload the app.
Remove the unnecessary reference to the NavigationBar in the App.controller file:
sap.ui.define([
"sap/ui/core/mvc/Controller",
'sap/m/MessageToast',
'sap/ui/ux3/NavigationBar',
'jquery.sap.global'
], function(Controller, MessageToast, NavigationBar, jQuery) {
...
Testing Responsiveness with Device Mode
Switch to Device mode by clicking the respective button or by pressing Ctrl + Shift + M .
Emulate different mobile devices by selecting different devices, or switch orientation from landscape to portrait.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
209
Analyzing Performance Problems and Memory Leaks
There are additional tabs that can help you to analyze performance problems or memory leaks. For more
information, refer to the documentation of the developer tools of your browser.
● Memory or Profiles
● Performance or Timeline
● Application or Resources
Related Information
Documentation of the Chrome DevTools on https://developers.google.com
Step 2: Technical Information Dialog
In this tutorial step, we will have a closer look at the "Technical Information Dialog". This tool comes in handy
whenever you want to know the technical details of the running application, and also has some other useful
features.
Preview
Opening the Example App and the Technical Information Dialog
1. Download the example app with errors from the Demo Kit at Troubleshooting and run the app.
210
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
2. Open the Technical Information Dialog by pressing Ctrl + Shift + Alt + P .
The dialog box shows information related to the app and provides access to additional support options.
Checking the OpenUI5 Version
When you run into problems with your app, you should check the OpenUI5 version that you're using. The feature
that you want to use may not be available in your version or may have some bugs that are already solved in a later
version.
1. Check the displayed version information for the
OpenUI5 Version.
2. Open the version overview at https://openui5.hana.ondemand.com/versionoverview.html to see if there are
newer patch levels or releases of OpenUI5.
3. Read the What's New [page 5] section in the documentation and check the Change Log to find information
about new features and bug fixes.
Note
You can view a specific version of the Demo Kit by adding the version number to the URL, for example,
https://openui5.hana.ondemand.com/1.38.8/.
For more information, see Versioning of OpenUI5 [page 30].
Checking the Device
The device on which you run the app may not be supported or might be detected incorrectly by OpenUI5. This can
lead to issues with responsiveness or device adaption.
1. Verify that the User Agent shown in the dialog box matches your device, browser, and operating system. If the
information is truncated because there is not enough space, you can see the full string as a tooltip. To copy the
information, use the Copy technical information to clipboard button.
2. Test the functionality on another device or by using the device emulation features that are offered in the
developer tools of your browser.
Turning On Debug Sources
The OpenUI5 libraries are included in your app in a compressed form. To be able to efficiently debug these
libraries, they have to be reloaded in their source format and with developer comments.
1. Select the Use Debug Sources checkbox and confirm reloading the app.
2. Open the developer tools of your browser
3. Choose Crtl + O and type the name of an OpenUI5 framework artifact to display its source code in debug
mode.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
211
Note
You may see additional errors and warnings in the developer console. These can help you investigate the
problem further.
For performance reasons, you should deactivate this feature again when you're done.
You can also select debug mode only for specific packages:
1. Next to the Use Debug Sources checkbox, choose Select specific modules to open the selection dialog box.
2. Select one or more modules in the module tree and notice that the value of the input field changes
accordingly.
3. Apply the selection and reload the app.
Only the selected modules are now loaded in debug mode.
Copying Technical Info
If you're really stuck or have found a bug, you can open a ticket. Choose the Copy technical information to clipboard
button to copy the technical details from this dialog box and then attach them to your message.
Accessing Other Tools
The Technical Information Dialog also includes links to Diagnostics and Support Assistant that we will discuss in the
following steps of this tutorial.
Related Information
Technical Information Dialog [page 932]
212
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Step 3: Support Assistant
In this tutorial step, we will have a closer look at Support Assistant. You can use this tool to check whether your app
is built according to the best practices with predefined rules.
Preview
Opening the Example App and Support Assistant
1. Download the example app with errors from the Demo Kit at Troubleshooting and run the app.
2. Activate the Support Assistant using one of the following options:
○ Open the Technical Information Dialog by pressing Ctrl + Shift + Alt + P and choose Activate
Support Assistant.
○ Use the URL parameter: sap-ui-support=true.
The Support Assistant toolbar opens in the footer of the app.
Analyzing and Fixing Issues
1. In the Support Assistant toolbar, choose Rules.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
213
2. In the Available Rules tab, select all rules, and choose Analyze.
You now see a list of issues.
3. Select Model: Unresolved binding path in the list of issues. In the issue details, you see the following message:
Element __xmlview0--LabelWithMissingI18NText Label_Missing_I18N_Text has the same value as the path.
Potential Error.
4. Open the i18n.properties file in your development environment and add the missing text.
[…]
item1Text=Item 1
item2Text=Item 2
selectEventMessage=Event "{0}" fired.
Label_Missing_I18N_Text=Label Text
For more information, see Walkthrough Step 8: Translatable Texts [page 102]
5. Restart the app and start the analysis again. The issue should now be gone.
Related Information
Support Assistant [page 946]
214
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Step 4: Diagnostics Window
In this tutorial step, we have a closer look at the Diagnostics window. It offers a wealth of information including
comprehensive technical information, a control tree, and debugging features.
Preview
Opening the Example App and the Diagnostics Window
1. Download the example app with errors from the Demo Kit at Troubleshooting and run the app.
2. Open the Diagnostics window by pressing Ctrl + Shift + Alt + S .
Improving App Performance
Let's say that you are facing a performance issue in your app, so let's check some performance-relevant settings in
the Diagnostics window:
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
215
1. Expand the Technical Information section and scroll down to view the loaded libraries.
2. Check if there are unused libraries. You can see that the example app loads the sap.ui.layout library even
though the layout control is not used.
3. To improve performance, remove the library from the manifest.json file in your development environment.
4. Scroll to the Configuration (bootstrap) section. You see that the preload method is set to synchronous
processing.
5. To improve performance, set the bootstrap parameter data-sap-ui-preload to async in the index.html
file.
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta charset="UTF-8">
<title>Heap Of Shards</title>
<script id="sap-ui-bootstrap"
src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-libs="sap.m, sap.f"
data-sap-ui-theme="sap_belize"
data-sap-ui-compatVersion="edge"
data-sap-ui-preload="async"
data-sap-ui-resourceroots='{"sap.ui.demo.HeapOfShards": "./",
"sap.ui.demo.DoesNotExist": "./DoesNotExist"}'>
</script>
[…]
Simulating UI Changes
The app contains a Do Something button and you want to make the button bigger. The control tree allows you to
test which width is the best.
1. Expand the Control Tree section. Make sure that you display both the app and the Diagnostics windows sideby-side or on different monitors. Otherwise the diagnostics window will go to the background.
2. Press and hold the Ctrl + Shift + Alt keys and click the Do Something button in the app. You see the
button blinking green.
3. In the control tree of the Diagnostics window, the button is selected and you can see its properties on the right.
4. Change the value of the width property to 100% and confirm with Enter .
The button width is automatically increased.
5. The changes that you make in the Diagnostics window are only temporary. To make your change permanent,
you have to change the app code.
Trying Different OpenUI5 Versions
If you find a bug in your application, you can easily check whether it has already been fixed in a newer OpenUI5
version. Just try the app with a different OpenUI5 version:
1. Expand the Technical Information section and check the loaded version.
2. Expand the Debugging section.
216
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
3. Choose Other from the Boot application with different UI5 version on next reload dropdown list.
4. Enter a custom URL, for example https://openui5.hana.ondemand.com/1.46.6/resources/sap-uicore.js.
5. Choose Activate Reboot URL, confirm the dialog box, and reload the app.
6. Reopen the Diagnostics window and expand the Technical Information section. The loaded OpenUI5 version is
now changed.
More Features
More features are waiting for you to discover in the Diagnostics window. For more information, see Diagnostics
[page 936].
Step 5: UI5 Inspector
In this tutorial step, we will have a closer look at UI5 Inspector - a plug-in specifically created for analyzing and
debugging OpenUI5 code.
With UI Inspector, you can find answers to the following questions, for example:
● What is the structure of your app?
● How are the elements related to each other?
● Which controls are involved when a function is performed?
● Which data is bound to a specific control and how (model and path)?
Note
UI5 Inspector is only available for the Google Chrome browser.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
217
Preview
Opening the Example App and the UI5 Inspector
1. Download UI5 Inspector from the Chrome Web Store and add it to your Chrome browser as a standard
extension .
2. Download the example app with errors from the Demo Kit at Troubleshooting and run the app.
3. Open the Developer Tools in Google Chrome by pressing F12 .
4. Choose the UI5 tab on the right side of the developer tools panel.
5. Choose Control Inspector.
You now see a list of all of the controls that are used in the current view of the app. When you select an entry,
you see the properties and their values in the Properties area on the right. You can analyze line by line without
being overwhelmed by too much information.
218
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Simulating UI Changes
The app contains a Do Something button with meaningless icon (sap-icon://action) and text. We want to use
the sap-icon://activate icon instead and change the text. With UI Inspector, we want to simulate how that will
effect the UI change.
1. Right-click the Do Something button and from the context menu select Inspect UI5 Control.
The corresponding line in the Control Inspector is highlighted and you can view its properties.
2. Double-click the value for the icon property, which is currently sap-icon://action.
3. Replace action with activate and confirm with Enter .
The icon on the button in the app is updated to show the new icon
.
4. Double-click the value for the text property and change the value to Activate.
5. The changes that you make in the UI5 Inspector are only temporary, and the icon will be reset to the default
when the page is reloaded. To make your change permanent, you have to change the app code.
Related Information
UI5 Inspector [page 974]
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
219
First-Aid Kit
This section contains the most common issues that you might face when developing OpenUI5 apps and how to
solve them.
An Empty Page Comes Up
You find yourself in one of these situations:
● The browser shows an empty page: there's no content and no error message is displayed
● An Uncaught Error message is shown in the developer console
220
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Preview
Figure 47: The browser displays an empty page and an Uncaught Error is issued in the console
Root Cause
This can happen for one of the following reasons:
● A critical reference error is prohibiting the app from starting
● A syntax error is stopping the execution of your application code
● A parsing error has occurred in an XML view
● The tag of the control is written with lowercase letters
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
221
Resolution
Console shows "ReferenceError: sap is not defined"
Have a look at the resource path in the bootstrap of the HTML page you are trying to open. The path to the file
sap-ui-core.js is probably incorrect and needs to point to the path where the OpenUI5 resources are located
(typically globally under /resources or locally under resources).
If you are running the code in SAP Web IDE, you have to configure the neo-app.json project descriptor (see
Create a neo-app.json Project Configuration File [page 45]).
Other development environments might need the resources to be copied to the server and referenced relatively to
the app (see Standard Variant for Bootstrapping [page 476]).
Alternatively, you can use the CDN version (see Variant for Bootstrapping from Content Delivery Network [page
476]).
Console shows SyntaxError: <error details>
A JavaScript error in the application code throws an exception and stops all subsequent execution. Take a look at
the error details: In most cases, the root cause is mentioned in the first line of the error message.
The stack trace can provide more context on the execution scope. Analyze it from thoroughly to find a line
referencing your application code and start debugging there.
Console shows Error: Invalid XML
If the XML view to be displayed cannot be parsed, OpenUI5 stops the execution and throws a parse error. Check
the XML view for namespace issues, typos, and missing closing tags. Do a schema validation with an XML validator
tool.
Console shows Uncaught Error: failed to load 'sap/m/xxxxx.js'
During the development on Microsoft Windows, your app works fine, but a soon as you deploy it on a Linux system,
only an empty page comes up.
This could happen if you wrote the tag of the control with lowercase letters, because Linux systems use casesensitive file names.
Correct Example
Incorrect Example
<Button text="Click me" />
<button text="Click me" />
Error message: Uncaught Error: failed to load
'sap/m/button.js'
Tip
Control tags always start with capital letters after the namespace like <Button>, <l:FixFlex>,
<f:SimpleForm>.
Aggregations always start with lowercase letters like <content>, <l:fixContent>, <f:content>
222
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Content or Control Is Not Visible
You find yourself in the situation that a control or the content of a control is not visible, but you don't see an error
message in the console.
Root Cause
This can happen for one of the following reasons:
● The element is not properly bound
● The visible property is set to false
● The height or width dimension is set to 0
Resolution
First, you should check if your control was rendered properly by using the developer tool of your browser to check
the DOM element. For information about how to use your browser tools, see the documentation of you browser or
check our Troubleshooting Tutorial Step 1: Browser Developer Tools [page 207].
Wrong binding
If you bound your control to a source, for example, an image control, the binding may not be resolved properly.
This can be caused by minor mistakes such as typos. We recommend using Diagnostics to debug your bindings.
For more information, see Diagnostics [page 936].
In the Diagnostics window, you can check whether you used a relative binding instead of an absolute one or vice
versa.
If you, for example, use a List control, you bind the list itself to an absolute path like items="{/Products}"
whereas the aggregations are bound to a relative path like title="{Name}". The actual path of the title
property is now {/Products/*Product_Index*/Name}.
If you used an absolute binding path like title="{/Name} for an aggregation instead of a relative one, the result
in the window would look like this:
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
223
Another common error related to binding is to refer to the default model instead of referring to a specific model.
This happens, for examples, if you forgot to add the model name to the binding declaration.
For example, you have two models in your application: the default model, which has no name and another model
named cartProducts. To bind to the cartProducts model you have to write the model name explicitly like
items="{cartProducts>/cartEntries}".
If you used the binding correctly Diagnostics displays the following:
224
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
If the model name is missing, you see the following:
visible property set to false
If you set the visible property of a control to false, it will not be rendered at all.
Nested controls inherit the value of the visible property from their parents. Therefore, if the control that you are
missing is nested in a parent control that is set to invisible, the nested control will also not be rendered.
You can fix this by setting the visible property of the parent control to true or by moving your missing control in
the XML view so that it is not longer nested inside an invisible control.
Dimensions set to 0
Most controls have the properties width and height. If one of them is explicitly set to 0 some controls may not be
displayed at all. Similar to the visible property, the value of width and height are also inherited from parent
controls, as long as you don't set an explicit value for these dimensions. If you, for example, set one of the
dimension values for a control to 100% it will have the same size as the parent control. And if the parent's width is 0
the nested control will also be 0.
As with the visible property, you can solve this by either increasing the size of the parent or setting fixed values
for the child (for example, 100px) instead of a relative value.
Request Fails Due to Same-Origin Policy (Cross-Origin
Resource Sharing (CORS))
If you use a remote URL in your code, for example a remote OData service, such as the publicly available
Northwind OData service, the browser may refuse to connect to a remote URL. Due to the same-origin policy,
browsers deny AJAX requests to service endpoints if the domain/subdomain, protocol, or port differ from the
app’s domain/subdomain, protocol, or port.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
225
Preview
Figure 48: Violations of the same-origin policy in Google Chrome
Root Cause
Normally, the remote system would be configured to send the cross-origin resource sharing (CORS) headers to
make the browser also allow direct access to remote URLs. However, if you, for example, use a Northwind OData
service, you cannot modify the publicly available service. Then when you try to execute XHR requests
(XMLHttpRequest) the browser prevents the call due to the same-origin policy.
Resolution
To solve the issue, you have the following options:
● Deploy the app on the same server or domain as the service that you want to call, so that both resources are in
the same origin
● Set the CORS-relevant response headers on the remote system (if possible)
● Disable the same-origin policy in the browser for local testing
In Google Chrome, you can easily disable the same-origin policy of Chrome by running Chrome with the
following command: [your-path-to-chrome-installation-dir]\chrome.exe --disable-websecurity --user-data-dir. Make sure that all instances of Chrome are closed before you run the
command. This allows all web sites to break out of the same-origin policy and connect to the remote service
directly.
Caution
This approach is not recommended for productive apps. Running Chrome this way for surfing on the
internet poses a security risk. However, it allows you to avoid the need of setting up a proxy at
development time or for testing purposes.
● Use a helper service from the same domain of your app as a proxy to call the real remote service as described
below
Using a Helper Service as a Proxy
A proxy is simply a service end point on the same domain of your app to overcome the restrictions. It receives
requests from the app, forwards them to another server, and finally returns the corresponding response from the
remote service.
SAP Web IDE and the SAP Cloud Platform offer destinations that allow you to easily connect to remote systems.
The destination to the Northwind OData service is an internet proxy made available inside the app at
226
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
<protocol>://<domain>/destinations/northwind/*. Any request that is sent to this location is forwarded
to http://services.odata.org automatically.
Requested URL
Forwarded To
/destinations/northwind/V2/Northwind/
http://services.odata.org/V2/Northwind/
Northwind.svc/$metadata
Northwind.svc/$metadata
/destinations/northwind/V2/Northwind/
http://services.odata.org/V2/Northwind/
Northwind.svc/Invoices
Northwind.svc/Invoices
The destination itself is configured inside the SAP Cloud Platform Cockpit. For more information, see Create a
Northwind Destination [page 47].
We describe the setup for the SAP Web IDE in the following sections. If you are using a different development
environment, you can either create a simple proxy service by yourself or use an existing one.
For SAP Web IDE, a neo-app.json file is needed to make sure that the destination and resource mapping are
available in the app. It has to be located in the root folder (webapp), on the same level as the
user.project.json file that is automatically created.
If it does not exist yet, create a neo-app.json file and reference the Northwind destination there. Just copy the
content of the code into that file and try to run the app again.
{
}
"routes": [
{
"path": "/destinations/northwind",
"target": {
"type": "destination",
"name": "northwind"
},
"description": "Northwind OData Service"
}
]
Note
If the file already exists, for example, because you already created it to map the OpenUI5 resources, just append
the destination to the existing route definitions.
In the manifest.json descriptor file of your app, you can now change the data source to use the remote
destination, for example:
{
"_version": "1.8.0",
"sap.app": {
...
"dataSources": {
"invoiceRemote": {
"uri": "/destinations/northwind/V2/Northwind/Northwind.svc/",
"type": "OData",
"settings": {
"odataVersion": "2.0"
}
}
}
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
227
}
},
"sap.ui": {
...
},
"sap.ui5": {
...
}
After this change, you can run the app in SAP Web IDE without disabling the same-origin policy of your browser.
The destination now manages the connection to the remote service.
App or Control Looks Odd
You find yourself in a situation that your app or a control looks different than you expected.
Root Cause
This can happen for one of the following reasons:
● An HTML file is missing the DOCTYPE specification (this leads, for example, to exceptionally high table
headers)
● Custom styles aren't working properly
● The theme you are using does not support the used libraries
Resolution
To solve the issue, you have the following options:
● Check whether the <!DOCTYPE html> tag is placed at the beginning of each HTML file, before the <html>
tag.
● Check if you have used a custom CSS in your app.
If you have used a custom CSS, it is probably interfering with the styling in the standard OpenUI5 theming
libraries.
Use the developer tools of your browser to inspect the element that has the wrong styling. In the HTML tab,
you can usually see which styles are applied to a DOM element. If you have styles in the list that are added by
your app, disable these styles in the debugger to see whether this solves the problem.
Note
OpenUI5-specific CSS classes and IDs all have an sapUi prefix, for example, sapUiButton.
If this does not solve the issue, check for inline styles that are applied to the element in the HTML code. You
can also try to isolate the control from the app to see whether there is an issue with the control instead of a
collision of styles.
228
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
● Check whether the theme you that you are using is supported in combination with the libraries that you are
using in your app. For more information, see Supported Combinations of Themes and Libraries [page 28] and
Deprecated Themes and Libraries [page 34].
Data Binding
In this tutorial, we will explain the concepts of data binding in OpenUI5.
OpenUI5 uses data binding to bind two data sources or information sources together to keep them in sync: All
changes in one source are also reflected in the other one.
For data binding, you need a model and a binding instance: The model instance holds the data and provides
methods to set the data or to retrieve the data from a server. It also provides a method for creating bindings to the
data. When this method is called, a binding instance is created, which contains the binding information and
provides an event, which is fired whenever the bound data changes. An element can listen to this event and update
its visualization according to the new data.
The UI uses data binding to bind controls to the model which holds the application data, so that the controls are
updated automatically whenever application data changes. Data binding is also used the other way round, when
changes in the control cause updates in the underlying application data, for example data entered by the user. This
is called two-way binding.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
229
Preview
Tip
You don't have to do all tutorial steps sequentially, you can also jump directly to any step you want. Just
download the code from the previous step, copy it to your workspace and make sure that the application runs
by calling the webapp/index.html file.
You can view and download the files for all steps in the Demo Kit at Data Binding. Depending on your
development environment you might have to adjust resource paths and configuration entries.
For more information check the following sections of the tutorials overview page (see Get Started: Setup and
Tutorials [page 38]):
230
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
● Downloading Code for a Tutorial Step [page 40]
● Adapting Code to Your Development Environment [page 40]
Related Information
Data Binding [page 673]
Model View Controller (MVC) [page 559]
Step 1: No Data Binding
In this step, we simply place some text on the screen using a standard sap.m.Text control. The text in this control
is a hard-coded part of the control's definition; therefore, this is not an example of data binding!
Preview
Figure 49: Screen with text
Coding
You can view and download all files in the Demo Kit at Data Binding - Step 1.
webapp/index.html (New)
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title>Data Binding Tutorial</title>
<script
id="sap-ui-bootstrap"
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
231
src="../resources/sap-ui-core.js"
data-sap-ui-theme="sap_belize"
data-sap-ui-libs="sap.m"
data-sap-ui-compatVersion="edge"
data-sap-ui-preload="async"
>
</script>
<script>
// Attach an anonymous function to the 'init' event
sap.ui.getCore().attachInit(function () {
// Create a text UI element that displays a hardcoded text string
new sap.m.Text({text: "Hi, my name is Harry Hawk"}).
placeAt("content");
});
</script>
<body class="sapUiBody" id="content">
</body>
</html>
Create a new folder webapp which will contain all sources of the app we will create throughout this tutorial, and
create the index.html file within this folder
Since the value of text property of the sap.m.Text control has been hard-coded, it is unrelated to any data that
might exist within a model object. Therefore, data binding is not being used here.
Step 2: Creating a Model
In this step, we create a model as container for the data on which your application operates.
The business data within a model can be defined using various formats:
● JavaScript Object Notation (JSON)
● Extensible Markup Language (XML)
● OData
● Your own custom format (not covered in this tutorial)
Note
There is also a special type of model called a "resource model". This model type is used as a wrapper object
around a resource bundle file. The names of such files must end with .properties and are used typically for
holding language-specific text.
We will use this in Step 6: Resource Models [page 239].
When JSON, XML and resource models are created, the data they contain is loaded in a single request (either from
a file stored locally on the client or by requesting it from a Web server). In other words, after the model's data has
been requested, the entire model is known to the application. These models are known as client-side models and
tasks such as filtering and sorting are performed locally on the client.
An OData model however, is a server-side model. This means that whenever an application needs data from the
model, it must be requested from the server. Such a request will almost never return all the data in the model,
typically because this would be far more data than is required by the client application. Consequently, tasks such
as sorting and filtering should always be delegated to the server.
In this tutorial, we will focus on JSON models since they are the simplest ones to work with.
232
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Preview
Figure 50: Screen with text derived from a model object (No visual changes to last step)
Coding
You can view and download all files in the Demo Kit at Data Binding - Step 2.
webapp/index.html
...
...
<script>
// Attach an anonymous function to the SAPUI5 'init' event
sap.ui.getCore().attachInit(function () {
// Create a JSON model from an object literal
var oModel = new sap.ui.model.json.JSONModel({
greetingText: "Hi, my name is Harry Hawk"
});
// Assign the model object to the SAPUI5 core
sap.ui.getCore().setModel(oModel);
// Create a text UI element that displays a hardcoded text string
new sap.m.Text({text: "{/greetingText}"})
.placeAt("content");
});
</script>
Create a new JSON model passing the data as object literal and store the resulting model instance in a local
variable called oModel.
Set oModel to be the default model within the entire OpenUI5 core.
This makes the model object globally available to all controls used within the application.
In this case we have bound the model object to the OpenUI5 core. This has been done for simplicity, but is not
considered good practice. Generally speaking, a model object holding business data should be bound to the view
that displays the data. We will correct this part of the code in the following steps.
Note
Models can be set on every control by calling setModel(). The model is then propagated to all aggregated
child controls (and their children, and so on…). All child control will then have access to that model
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
233
The text that is displayed on the UI is still not taken from the model - we will do this in the next step.
Related Information
Models [page 560]
JSON Model [page 643]
Step 3: Create Property Binding
Although there is no visible difference, the text on the screen is now derived from model data.
Preview
Figure 51: Screen with text derived from various sources (No visual changes to last step)
Coding
You can view and download all files in the Demo Kit at Data Binding - Step 3.
webapp/index.html
...
<script>
// Attach an anonymous function to the SAPUI5 'init' event
sap.ui.getCore().attachInit(function () {
// Create a JSON model from an object literal
var oModel = new sap.ui.model.json.JSONModel({
greetingText: "Hi, my name is Harry Hawk"
});
// Assign the model object to the SAPUI5 core
sap.ui.getCore().setModel(oModel);
234
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
// Display a text element whose text is derived
// from the model object
new sap.m.Text({ text : "{/greetingText}" }).
placeAt("content");
...
});
</script>
The text property of the sap.m.Text control is set to the value {/greetingText}. The curly brackets enclosing
a binding path (binding syntax) are automatically interpreted as a binding. These binding instances are called
PropertyBindings. In this case, the control's text property is bound to the greetingText property at the root
of the default model, as the slash (/) at the beginning of the binding path denotes an absolute binding path.
Related Information
Binding Controls to the Model [page 651]
Property Binding [page 688]
Step 4: Two-Way Data Binding
In the examples used so far, we have used a read-only field to display the value of a model property. We will now
change the user interface so that the first and last name fields are displayed using sap.m.Input fields and an
additional check box control is used to enable or disable both input fields. This arrangement illustrates a feature
known as "two-way data binding". Now that the view contains more controls, we will also move the view definition
into an XML file.
Preview
Figure 52: Input fields can be enabled or disabled
Coding
You can view and download all files in the Demo Kit at Data Binding - Step 4.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
235
webapp/view/App.view.xml (New)
<mvc:View xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc">
<Panel headerText="{/panelHeaderText}" class="sapUiResponsiveMargin" width="auto">
<content>
<Label text="First Name" class="sapUiSmallMargin" />
<Input value="{/firstName}" valueLiveUpdate="true" width="200px" enabled="{/
enabled}" />
<Label text="Last Name" class="sapUiSmallMargin" />
<Input value="{/lastName}" valueLiveUpdate="true" width="200px" enabled="{/
enabled}" />
<CheckBox selected="{/enabled}" text="Enabled" />
</content>
</Panel>
</mvc:View>
We create a new view folder in our app and a new file for our XML view inside the app folder.
webapp/index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title>SAPUI5 Data Binding Tutorial</title>
<script
id="sap-ui-bootstrap"
src="../../../../../../../../../resources/sap-ui-core.js"
data-sap-ui-theme="sap_belize"
data-sap-ui-libs="sap.m"
data-sap-ui-compatVersion="edge"
data-sap-ui-preload="async"
data-sap-ui-resourceroots='{ "sap.ui.demo.db": "./" }'
>
</script>
<script>
// Attach an anonymous function to the SAPUI5 'init' event
sap.ui.getCore().attachInit(function () {
// Create a JSON model from an object literal
var oModel = new sap.ui.model.json.JSONModel({
firstName: "Harry",
lastName: "Hawk",
enabled: true,
panelHeaderText: "Data Binding Basics"
});
// Assign the model object to the SAPUI5 core
sap.ui.getCore().setModel(oModel);
// Display the XML view called "App"
new sap.ui.core.mvc.XMLView({ viewName : "sap.ui.demo.db.view.App" })
.placeAt("content");
});
</script>
</head>
<body class="sapUiBody" id="content">
</body>
</html>
236
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
We add the parameter data-sap-ui-resourcerouts to the bootstrap.
We delete the code that assigned the sap.m.Text field to the UI and add an XML view that is identified by its
resource name.
You can now refresh the application preview and select or deselect the checkbox. You will see that the input fields
are automatically enabled or disabled in response to the state of the checkbox.
It is clear that we have not written any code to transfer data between the user interface and the model, yet the
Input controls are enabled or disabled according to the state of the checkbox. This behaviour is the result of the
fact that all OpenUI5 models implement two-way data binding, and for JSON Models, two-way binding is the
default behavior.
Two things are happening here:
● Data binding allows the property of a control to derive its value from any suitable property in a model.
● OpenUI5 automatically handles the transport of data both from the model to the controls, and back from the
controls to the model. This is called two-way binding.
Related Information
Data Binding [page 673]
Step 5: One-Way Data Binding
In contrast to the two-way binding behavior shown above, one-way data binding is also possible. Here, data is
transported in one direction only: from the model, through the binding instance to the consumer (usually the
property of a control), but never in the other direction. In this example, we will change the previous example to use
one-way data binding. This will illustrate how the flow of data from the user interface back to the model can be
switched off if required.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
237
Preview
Figure 53: Two-way data binding disabled for the checkbox
Coding
You can view and download all files in the Demo Kit at Data Binding - Step 5.
webapp/index.html
...
...
<script>
var oModel = new sap.ui.model.json.JSONModel({
firstName : "Harry",
lastName : "Hawk",
enabled
: true,
panelHeaderText : "Data Binding Basics"
});
oModel.setDefaultBindingMode(sap.ui.model.BindingMode.OneWay);
// Assign the model object to the SAPUI5 core
sap.ui.getCore().setModel(oModel);
Insert the single highlighted line immediately after the creation of the model object in index.html.
Now, no matter what state the checkbox is in, the input fields remain open for input because one-way data binding
ensures that data flows only from the model to the UI, but never in the other direction.
The binding mode (one-way or two-way) is set on the model itself. Therefore, unless you specifically alter it, a
binding instance will always be created using the model's default binding mode.
Should you wish to alter the binding mode, then there are two ways of doing this:
● Alter the model's default binding mode. This is the approach used above.
● Specify the data binding mode for a specific binding instance by using the oBindingInfo.mode parameter.
This change applies only to this data binding instance. Any other binding instances will continue to use the
model's default binding mode. For more information, see API Reference:
sap.ui.base.ManagedObject.bindProperty.
Note
There are two important points to understand about alterations to a model object's data binding mode:
238
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
● If you alter the default binding mode of a model (as in the example above), then unless you explicitly say
otherwise, all binding instances created after that point in time will use the altered binding mode.
● Altering a model's default binding mode has no effect on already existing binding instances.
Step 6: Resource Models
Business applications also require language-specific (translatable) texts used as labels and descriptions on the
user interface.
The example we used at the start of this tutorial was overly simplistic as we stored language-specific text directly
in a JSON model object. Generally speaking, unless language-specific text is derived directly from a back-end
system, it is not considered good programming practice to place translatable texts directly into a model. So let's
correct this situation by placing all translatable texts (such as field labels) into a resource bundle.
Preview
Figure 54: Texts derived from the resource model (No visual change to last step)
Coding
You can view and download all files in the Demo Kit at Data Binding - Step 6.
webapp/index.html
...
<script>
var oModel = new sap.ui.model.json.JSONModel({
firstName : "Harry",
lastName : "Hawk",
enabled
: true
});
// Assign the model object to the SAPUI5 core
sap.ui.getCore().setModel(oModel);
// Create a resource bundle for language specific texts
var oResourceModel = new sap.ui.model.resource.ResourceModel({
bundleName : "sap.ui.demo.db.i18n.i18n"
});
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
239
...
// Assign the model object to the SAPUI5 core using the name "i18n"
sap.ui.getCore().setModel(oResourceModel, "i18n");
// Display the XML view called "App"
new sap.ui.core.mvc.XMLView({ viewName : "sap.ui.demo.db.views.App" })
.placeAt("content");
</script>
Since we are creating a resource model, the file name is assumed to have the extension .properties; this does
not need to be stated explicitly. The resource model is set to the core using the model name i18n.
Note
Remove , panelHeaderText : "Data Binding Basics" from the model definition in the index.html
file. This text is now moved to the resource model.
webapp/i18n/i18n.properties (New)
# Field labels
firstName=First Name
lastName=Last Name
enabled=Enabled
# Screen titles
panelHeaderText=Data Binding Basics
Create a new folder i18n, and a new file i18n.properties within and add the code above.
The panelHeaderText property has been moved from the JSON model into the i18n resource bundle, also the
field labels are no longer hard coded in the XML view. This is because all of these text fields need to be translated.
Language-specific text stored in resource models obeys the Java convention for internationalization (i18n).
webapp/view/App.view.xml
<mvc:View xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc">
<Panel headerText="{i18n>panelHeaderText}" class="sapUiResponsiveMargin"
width="auto">
<content>
<Label text="{i18n>firstName}" class="sapUiSmallMargin" />
<Input value="{/firstName}" valueLiveUpdate="true" width="200px" enabled="{/
enabled}" />
<Label text="{i18n>lastName}" class="sapUiSmallMargin" />
<Input value="{/lastName}" valueLiveUpdate="true" width="200px" enabled="{/
enabled}" />
<CheckBox selected="{/enabled}" text="{i18n>enabled}" />
</content>
</Panel>
</mvc:View>
240
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Modify the data binding for the panel header and the labels in App.view.xml to include the model name. Notice
that a "greater than" character separates the model name and the property name, and that i18n property names
must not to start with a slash character!
You could use multiple model instances by using different model names. The model name could be set as second
parameter using the setModel(oResourceModel,“i18n”) method. The model is then propagated under this
name to all aggregated child controls (and their children, and so on…). All these controls have access to this model
under the name i18n as well as to the JSONModel (default model, which has no name).
Related Information
Resource Model [page 648]
Step 7: (Optional) Resource Bundles and Multiple Languages
The reason we have resource bundles is to allow an app to run in multiple languages without the need to change
any code. To demonstrate this feature, we will create a German version of the app – in fact all we need to do is
create a German version of the resource bundle file. No code changes are needed.
Preview
Figure 55: German version of our UI
Coding
You can view and download all files in the Demo Kit at Data Binding - Step 7.
webapp/i18n/i18n_de.properties (New)
# Field labels
firstName=Vorname
lastName=Nachname
enabled=Aktiviert
# Screen titles
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
241
panelHeaderText=Data Binding Grundlagen
In the i18n folder, take a copy of the file i18n.properties and call it i18n_de.properties. Change the
English text to the German text.
To test the outcome, change the default language of your browser to German and refresh your preview.
Related Information
Localization [page 846]
Step 8: Binding Paths: Accessing Properties in Hierarchically
Structured Models
In step 6 , we stated that the fields in a resource model are arranged in a flat structure; in other words, there can be
no hierarchy of properties; however, this is true only for resource models. The properties within JSON and OData
models almost always are arranged in a hierarchical structure. Therefore, we should take a look at how to reference
fields in a hierarchically structured model object.
Preview
Figure 56: Second panel with additional data
Coding
You can view and download all files in the Demo Kit at Data Binding - Step 8.
242
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/index.html
...
...
<script>
// -----------------------------------------------------------------------// Attach an anonymous function to the 'init' event
sap.ui.getCore().attachInit(function () {
var oModel = new sap.ui.model.json.JSONModel({
firstName : "Harry",
lastName : "Hawk",
enabled
: true,
address
: {
street : "Dietmar-Hopp-Allee 16",
city : "Walldorf",
zip : "69190",
country : "Germany"
}
});
The JSON model object now contains an additional sub-object called address. Within this object are four
properties: street, city, zip, and country.
webapp/view/App.view.xml
<mvc:View xmlns="sap.m" xmlns:l="sap.ui.layout" xmlns:mvc="sap.ui.core.mvc">
<Panel headerText="{i18n>panel1HeaderText}" class="sapUiResponsiveMargin"
width="auto">
<content>
<Label text="{i18n>firstName}" class="sapUiSmallMargin" />
<Input value="{/firstName}" valueLiveUpdate="true" width="200px" enabled="{/
enabled}" />
<Label text="{i18n>lastName}" class="sapUiSmallMargin" />
<Input value="{/lastName}" valueLiveUpdate="true" width="200px" enabled="{/
enabled}" />
<CheckBox selected="{/enabled}" text="{i18n>enabled}" />
</content>
</Panel>
<Panel headerText="{i18n>panel2HeaderText}" class="sapUiResponsiveMargin"
width="auto">
<content>
<l:VerticalLayout>
<Label class="sapUiSmallMargin" text="{i18n>address}:" />
<Text class="sapUiSmallMargin"
text="{/address/street}\n{/address/zip} {/address/city}\n{/address/
country}"
width="200px" />
</l:VerticalLayout>
</content>
</Panel>
</mvc:View>
We add a new panel to the XML view with a new Label and Text pair of elements.
The text property of the Label element is bound to the i18n resource bundle field address.
The text property of the Text element is bound to three i18n properties: /address/street, /address/zip, /
address/city, and /address/country. The resulting address format is achieved by separating each one of
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
243
these model property references with a hard-coded newline character while zip and city are separated by a
space.
webapp/i18n/i18n.properties
# Field labels
firstName=First Name
lastName=Last Name
enabled=Enabled
address=Address
# Screen titles
panel1HeaderText=Data Binding Basics
panel2HeaderText=Address Details
webapp/i18n/i18n_de.properties
# Field labels
firstName=Vorname
lastName=Nachname
enabled=Aktiviert
address=Adresse
# Screen titles
panel1HeaderText=Data Binding Grundlagen
panel2HeaderText=Adressdetails
Note
The resource bundle files now contain new properties for the Address and a new panel header text. Both panel
properties have been numbered.
In the XML view, inside the curly brackets for the binding path of the Text element, notice that the first
character is a forward slash. This is required for binding paths that make absolute references to properties in
JSON and OData models, but must not be used for resource models. After the first forward slash character, the
binding path syntax uses the object names and the property name separated by forward slash characters ({/
address/street}).
As has been mentioned previously, all binding path names are case-sensitive.
Related Information
JSON Model [page 643]
244
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Step 9: Formatting Values
We also want to provide our users a way of contacting Harry Hawk. Therefore we will add a link that sends an email to Harry. To achieve that we will convert our data in the model to match the
sap.m.URLHelper.normalizeEmail API. As soon as the user changes the name, the e-mail will also change.
We will need a custom formatter function for this.
Preview
Figure 57: Address with e-mail link
Coding
You can view and download all files in the Demo Kit at Data Binding - Step 9.
webapp/controller/App.controller.js (New)
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function (Controller) {
"use strict";
});
return Controller.extend("sap.ui.demo.db.controller.App", {
formatMail: function(sFirstName, sLastName) {
var oBundle = this.getView().getModel("i18n").getResourceBundle();
return sap.m.URLHelper.normalizeEmail(
sFirstName + "." + sLastName + "@example.com",
oBundle.getText("mailSubject", [sFirstName]),
oBundle.getText("mailBody"));
}
});
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
245
Create a new folder controller within your webapp folder as a general location for all controller files for this app
and create a new file App.controller.js.
In our custom formatter, we define the first and last name that are currently in the model as function parameters.
When a user changes the data in the model by entering a different name in the input fields, our formatter will be
invoked automatically by the framework. This makes sure that the UI is in sync with the data model.
In the formatter function, we use the sap.m.URLHelper.normalizeEmail function that expects an e-mail
address, a mail subject and a text body. When a user chooses the link, the default email client will open with these
parameters.For more information, see API Reference: sap.m.URLHelper.normalizeEmail. The mailSubject
resource bundle text will contain a placeholder for the first name of the recipient (see below). Therefore, we
provide the name with [sFirstName].
Note
For a detailed description of the e-mail link format, see https://developer.mozilla.org/de/docs/Web/Guide/
HTML/Email_links.
webapp/view/App.view.xml
<mvc:View xmlns="sap.m" xmlns:l="sap.ui.layout"
xmlns:mvc="sap.ui.core.mvc"
controllerName="sap.ui.demo.db.controller.App">
<Panel headerText="{i18n>panel1HeaderText}" class="sapUiResponsiveMargin"
width="auto">
<content>
<Label text="{i18n>firstName}" class="sapUiSmallMargin" />
<Input value="{/firstName}" valueLiveUpdate="true" width="200px"
enabled="{/enabled}" />
<Label text="{i18n>lastName}" class="sapUiSmallMargin" />
<Input value="{/lastName}" valueLiveUpdate="true" width="200px"
enabled="{/enabled}" />
<CheckBox selected="{/enabled}" text="{i18n>enabled}" />
</content>
</Panel>
<Panel headerText="{i18n>panel2HeaderText}" class="sapUiResponsiveMargin"
width="auto">
<content>
<l:VerticalLayout>
<Label class="sapUiSmallMargin" text="{i18n>address}:" />
<Text class="sapUiSmallMarginBegin sapUiSmallMarginBottom"
text="{/address/street}\n{/address/zip} {/address/city}\n{/
address/country}"
width="200px" />
<Link class="sapUiSmallMarginBegin"
href="{
parts: [
'/firstName',
'/lastName'
],
formatter: '.formatMail'
}"
text="{i18n>sendEmail}"/>
</l:VerticalLayout>
</content>
</Panel>
</mvc:View>
246
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
For more complex bindings we cannot use the simple binding syntax with the curly braces anymore. The href
property of the Link element now contains an entire object inside the string value. In this case, the object has two
properties:
● parts
This is a JavaScript array in which each element is an object containing a path property. The number and
order of the elements in this array corresponds directly to the number and order of parameters expected by
the formatMail function.
● formatter
A reference to the function that receives the parameters listed in the parts array. Whatever value is returned
by the formatter function becomes the value set for this property. The dot ( formatMail) at the beginning of
the formatter tellsOpenUI5 to look for a formatMail function on the controller instance of the view. If you do
not use the dot, the function will be resolved by looking into the global namespace.
Note
When using formatter functions, the binding is automatically switched to "one-way". So you can’t use a
formatter function for "two-way" scenarios, but you can use data types (which will be explained in the following
steps).
webapp/i18n/i18n.properties
…
# Screen titles
panel1HeaderText=Data Binding Basics
panel2HeaderText=Adress Details
# E-mail
sendEmail=Send Mail
mailSubject=Hi {0}!
mailBody=How are you?
webapp/i18n/i18n_de.properties
…
# Screen titles
panel1HeaderText=Data Binding Grundlagen
panel2HeaderText=Adress Details
# E-mail
sendEmail=E-mail versenden
mailSubject=Hallo {0}!
mailBody=Wie geht es dir?
And we add the missing texts to the properties files
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
247
Related Information
Custom Formatter Functions [page 718]
Step 10: Property Formatting Using Data Types
OpenUI5 provides a set of simple data types such as Boolean, Currency, Date and Float. These data types can
then be applied to controls in order to ensure that the value presented on the screen is formatted correctly, and, if
the field is open for input, that the value entered by the user adheres to the requirements of that data type. We will
now add a new field called Sales to Date of type Currency.
Preview
Figure 58: New Sales to Date input field
Coding
You can view and download all files in the Demo Kit at Data Binding - Step 10.
webapp/index.html
<script>
var oModel = new sap.ui.model.json.JSONModel({
firstName : "Harry",
lastName : "Hawk",
enabled
: true,
address
: {
248
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
street : "Dietmar-Hopp-Allee 16",
city : "Walldorf",
zip : "69190",
country : "Germany"
},
"salesToDate" : 12345.6789,
"currencyCode" : "EUR"
});
...
We create two new model properties salesToDate and currencyCode.
webapp/view/App.view.xml
...
<Panel headerText="{i18n>panel2HeaderText}" class="sapUiResponsiveMargin"
width="auto">
<content>
<l:VerticalLayout>
<Label class="sapUiSmallMargin" text="{i18n>address}:" />
<Text
class="sapUiSmallMarginBegin sapUiSmallMarginBottom"
text="{/address/street}\n{/address/zip} {/address/city}\n{/
address/country}"
width="200px" />
<Link class="sapUiSmallMarginBegin"
href="{
parts: [
'/firstName',
'/lastName'
],
formatter: '.formatMail'
}"
text="{i18n>sendEmail}"/>
</l:VerticalLayout>
<l:VerticalLayout>
<Label text="{i18n>salesToDate}:" class="sapUiSmallMargin" />
<Input width="200px" enabled="{/enabled}" description="{/
currencyCode}"
value="{ parts: [{path: '/salesToDate'}, {path: '/
currencyCode'}],
type: 'sap.ui.model.type.Currency',
formatOptions: {showMeasure: false } }"/>
</l:VerticalLayout>
</content>
</Panel>
</mvc:View>
A new pair of Label and Input elements have been created for the salesToDate model property. The
description property of the Input element has been bound to the currencyCode model property. The value
property of the Input element has been bound to the model properties salesToDate and currencyCode. The
{showMeasure: false} parameter switches off the display of the currency symbol within the input field itself.
This is not needed because it is being displayed using the Input element's description property.
webapp/i18n/i18n.properties
# Field labels
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
249
firstName=Vorname
lastName=Nachname
enabled=Enabled
address=Address
salesToDate=Sales to Date...
webapp/i18n/i18n_de.properties
# Field labels
firstName=Vorname
lastName=Nachname
enabled=Aktiviert
address=Adresse
salesToDate=Verkäufe bis zum heutigen Datum
...
Add the missing texts to the properties files.
Related Information
Using the Data Binding Type System [page 694]
Step 11: Validation Using the Message Manager
So far, we have created a currency field that can format itself correctly. The currency data type also has the ability
to validate that user input adheres to to the requirements of a currency; however, data type validation functions
are managed by OpenUI5, which of itself has no mechanism for reporting error messages back to the UI; therefore,
we need a mechanism for reporting error messages raised by validation functions back to the user. In this step, we
will connect the entire view to a feature known as the "Message Manager". Once this connection is established, any
validation error messages generated based on the user input will be passed to the message manager which in turn
will connect them to the appropriate view and control that caused the error.
250
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Preview
Figure 59: A message appears
Coding
You can view and download all files in the Demo Kit at Data Binding - Step 11.
webapp/index.html
...
...
<script>
// Assign the model object to the SAPUI5 core
sap.ui.getCore().setModel(oModel);
var oResourceBundle = new sap.ui.model.resource.ResourceModel({
bundleName: "sap.ui.demo.db.i18n.i18n"
});
sap.ui.getCore().setModel(oResourceBundle, "i18n");
// Create view
var oView = new sap.ui.core.mvc.XMLView({ viewName :
"sap.ui.demo.db.view.App" });
// Register the view with the message manager
sap.ui.getCore().getMessageManager().registerObject(oView, true);
// Insert the view into the DOM
oView.placeAt("content");
});
</script>
</head>
<body class="sapUiBody" id="content">
</body>
</html>
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
251
The changes to the coding are minimal:
● The XML view is now created as a named object called oView.
● The view object oView is registered with the MessageManager.
● Once registered, the XML view is then inserted into the DOM as before.
You can now enter a non-numeric value into the Sales To Date field and either press Enter or move the focus to a
different UI control. This action triggers either the onenter or onchange event and then OpenUI5 executes the
validation function belonging to the sap.ui.model.type.Currency data type.
Now that the view has been registered with the MessageManager, any validation error messages will be picked up
by the MessageManager, which in turn checks its list of registered objects and then passes the error message
back to the correct view for display.
Note that the field in error has a red border:
However, the error message itself will only be displayed when that particular field has focus:
Related Information
Managing UI and Server Messages [page 791]
Step 12: Aggregation Binding Using Templates
Aggregation binding (or "list binding") allows a control to be bound to a list within the model data and allows
relative binding to the list entries by its child controls.
It will automatically create as many child controls as are needed to display the data in the model using one of the
following two approaches:
● Use template control that is cloned as many times as needed to display the data.
● Use a factory function to generate the correct control per bound list entry based on the data received at
runtime.
252
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Preview
Figure 60: List with aggregation binding
Coding
You can view and download all files in the Demo Kit at Data Binding - Step 12.
webapp/index.html
...
<script>
// Attach an anonymous function to the SAPUI5 'init' event
sap.ui.getCore().attachInit(function () {
var oProductModel = new sap.ui.model.json.JSONModel();
oProductModel.loadData("./model/Products.json");
sap.ui.getCore().setModel(oProductModel, "products");
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
253
...
var oModel = new sap.ui.model.json.JSONModel({
firstName: "Harry",
lastName: "Hawk",
enabled: true,
address: {
street: "Dietmar-Hopp-Allee 16",
city: "Walldorf",
zip: "69190",
country: "Germany"
},
"salesToDate" : 12345.6789,
"currencyCode" : "EUR"
});
webapp/view/App.view.xml
...
<Input width="200px" enabled="{/enabled}" description="{/
currencyCode}"
value="{ parts: [{path: '/salesToDate'}, {path: '/
currencyCode'}],
type: 'sap.ui.model.type.Currency',
formatOptions: {showMeasure: false } }"/>
</l:VerticalLayout>
</content>
</Panel>
<Panel headerText="{i18n>panel3HeaderText}" class="sapUiResponsiveMargin"
width="auto">
<content>
<List headerText="{i18n>productListTitle}" items="{products>/Products}">
<items>
<ObjectListItem title="{products>ProductName}"
number="{
parts:
[
{path: 'products>UnitPrice'},
{path: '/currencyCode'}
],
type: 'sap.ui.model.type.Currency',
formatOptions: { showMeasure: false }
}"
numberUnit="{/currencyCode}">
<attributes>
<ObjectAttribute text="{products>QuantityPerUnit}"/>
<ObjectAttribute title="{i18n>stockValue}"
text="{parts: [
{path: 'products>UnitPrice'},
{path:
'products>UnitsInStock'},
{path: '/currencyCode'}
],
formatter: '.formatStockValue'
}" />
</attributes>
</ObjectListItem>
</items>
</List>
</content>
</Panel>
...
We add a new panel to the view.
254
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/controller/App.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/type/Currency"
], function (Controller, Currency) {
"use strict";
return Controller.extend("sap.ui.demo.db.controller.App", {
formatMail: function(sFirstName, sLastName) {
var oBundle = this.getView().getModel("i18n").getResourceBundle();
return sap.m.URLHelper.normalizeEmail(
sFirstName + "." + sLastName + "@example.com",
oBundle.getText("mailSubject", [sFirstName]),
oBundle.getText("mailBody"));
},
formatStockValue: function(fUnitPrice, iStockLevel, sCurrCode) {
var sBrowserLocale = sap.ui.getCore().getConfiguration().getLanguage();
var oLocale = new sap.ui.core.Locale(sBrowserLocale);
var oLocaleData = new sap.ui.core.LocaleData(oLocale);
var oCurrency = new Currency(oLocaleData.mData.currencyFormat);
return oCurrency.formatValue([fUnitPrice * iStockLevel, sCurrCode],
"string");
}
});
});
webapp/model/Products.json (New)
{ "Products": [ {
"ProductID": 1,
"ProductName": "Chai",
"SupplierID": 1,
"CategoryID": 1,
"QuantityPerUnit": "10 boxes x 20 bags",
"UnitPrice": "18.0000",
"UnitsInStock": 39,
"UnitsOnOrder": 0,
"ReorderLevel": 10,
"Discontinued": false
}, {
"ProductID": 2,
"ProductName": "Chang",
"SupplierID": 1,
"CategoryID": 1,
"QuantityPerUnit": "24 - 12 oz bottles",
"UnitPrice": "19.0000",
"UnitsInStock": 17,
"UnitsOnOrder": 40,
"ReorderLevel": 25,
"Discontinued": true
}, {
"ProductID": 3,
"ProductName": "Aniseed Syrup",
"SupplierID": 1,
"CategoryID": 2,
"QuantityPerUnit": "12 - 550 ml bottles",
"UnitPrice": "10.0000",
"UnitsInStock": 0,
"UnitsOnOrder": 70,
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
255
}
"ReorderLevel": 25,
"Discontinued": false
}, {
"ProductID": 4,
"ProductName": "Chef Anton's Cajun Seasoning",
"SupplierID": 2,
"CategoryID": 2,
"QuantityPerUnit": "48 - 6 oz jars",
"UnitPrice": "22.0000",
"UnitsInStock": 53,
"UnitsOnOrder": 0,
"ReorderLevel": 0,
"Discontinued": false
}, {
"ProductID": 5,
"ProductName": "Chef Anton's Gumbo Mix",
"SupplierID": 2,
"CategoryID": 2,
"QuantityPerUnit": "36 boxes",
"UnitPrice": "21.3500",
"UnitsInStock": 0,
"UnitsOnOrder": 0,
"ReorderLevel": 0,
"Discontinued": true
}]
We now use a new JSON model file for product data.
webapp/i18n/i18n.properties
...
# Screen titles
panel1HeaderText=Data Binding Basics
panel2HeaderText=Adress Details
panel3HeaderText=Aggregation Binding
# Invoice List
invoiceListTitle=Invoices
statusA=New
statusB=In Progress
statusC=Done
# Product list
productListTitle=Product List
stockValue=Current Stock Value
webapp/i18n/i18n_de.properties
...
# Screen titles
panel1HeaderText=Data Binding Basics
panel2HeaderText=Adressdetails
panel3HeaderText=Aggregation Binding
# Invoice List
invoiceListTitle=Rechnungen
statusA=Neu
statusB=Laufend
statusC=Abgeschlossen
256
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
# Product list
productListTitle=Artikelliste
stockValue=Lagerbestand Wert
We add the missing texts.
Related Information
List Binding (Aggregation Binding) [page 680]
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
257
Step 13: Element Binding
Now we want to do something with that newly generated list. In most cases you will use a list to allow the selection
of an item and then show the details of that item elsewhere. In order to achieve this, we use a form with relatively
bound controls and bind it to the selected entity via element binding.
Preview
Figure 61: Element binding implemented, product details displayed per item
Coding
You can view and download all files in the Demo Kit at Data Binding - Step 13.
258
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/view/App.view.xml
...
</items>
</List>
</content>
</Panel>
<Panel id="productDetailsPanel" headerText="{i18n>panel4HeaderText}"
class="sapUiResponsiveMargin" width="auto">
<l:Grid defaultSpan="L3 M6 S12" containerQuery="true">
<Label text="{i18n>ProductID}:" />
<Input value="{products>ProductID}" />
<Label text="{i18n>ProductName}:" />
<Input value="{products>ProductName}" />
<Label text="{i18n>QuantityPerUnit}:" />
<Input value="{products>QuantityPerUnit}" />
<Label text="{i18n>UnitPrice}:" />
<Input value="{products>UnitPrice}" />
<Label text="{i18n>UnitsInStock}:" />
<Input value="{products>UnitsInStock}" />
<Label text="{i18n>Discontinued}:" />
<CheckBox selected="{products>Discontinued}" />
</l:Grid>
</Panel>
</mvc:View>
Now we have an empty form. In order to fill this form with data, we will bind the whole panel to the path of the
element which we clicked in the list. We need to add a press-event handler to the items in the list.
webapp/view/App.views.xml
...
<Panel headerText="{i18n>panel4HeaderText}" class="sapUiResponsiveMargin"
width="auto">
<content>
<List headerText="{i18n>productListTitle}" items="{products>/Products}">
<items>
<ObjectListItem
press=".onItemSelected"
type="Active"
title="{products>ProductName}"
number="{ parts: [{path: 'products>UnitPrice'},
{path: '/currencyCode'}],
type: 'sap.ui.model.type.Currency',
formatOptions: { showMeasure: false }
}"
numberUnit="{/currencyCode}">
<attributes>
...
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
259
webapp/controller/App.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/type/Currency"
], function (Controller, Currency) {
"use strict";
return Controller.extend("sap.ui.demo.db.controller.App", {
formatMail: function(sFirstName, sLastName) {
var oBundle = this.getView().getModel("i18n").getResourceBundle();
return sap.m.URLHelper.normalizeEmail(
sFirstName + "." + sLastName + "@example.com",
oBundle.getText("mailSubject", [sFirstName]),
oBundle.getText("mailBody"));
},
formatStockValue: function(fUnitPrice, iStockLevel, sCurrCode) {
var sBrowserLocale = sap.ui.getCore().getConfiguration().getLanguage();
var oLocale = new sap.ui.core.Locale(sBrowserLocale);
var oLocaleData = new sap.ui.core.LocaleData(oLocale);
var oCurrency = new Currency(oLocaleData.mData.currencyFormat);
return oCurrency.formatValue([fUnitPrice * iStockLevel, sCurrCode],
"string");
},
});
});
onItemSelected: function(oEvent) {
var oSelectedItem = oEvent.getSource();
var oContext = oSelectedItem.getBindingContext("products");
var sPath = oContext.getPath();
var oProductDetailPanel = this.byId("productDetailsPanel");
oProductDetailPanel.bindElement({ path: sPath, model: "products" });
}
In the controller, we bind the newly created panel to the correct item whenever it is pressed.
We can now click on an element in the list and see its details in the panel below. We can even edit these details and
these changes are directly shown in the list because we use two-way binding.
Note
Element bindings can also be relative to its parent context.
webapp/i18n/i18n.properties
...
# Screen titles
panel1HeaderText=Data Binding Basics
panel2HeaderText=Adress Details
panel3HeaderText=Aggregation Binding
panel4HeaderText=Product Details
# Product list
productListTitle=Product List
stockValue=Current Stock Value
# Product Details
ProductID=Product ID
ProductName=Product Name
260
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
QuantityPerUnit=Quantity per Unit
UnitPrice=Unit Price
UnitsInStock=Number of Units in Stock
Discontinued=Discontinued
webapp/i18n/i18n_de.properties
# Screen titles
panel1HeaderText=Data Binding Grundlagen
panel2HeaderText=Adressdetails
panel3HeaderText=Aggregation Binding
panel4HeaderText=Produktdetails
# Product list
productListTitle=Artikelliste
stockValue=Lagerbestand Wert
# Product Details
ProductID=Produkt-ID
ProductName=Produktname
QuantityPerUnit=Mege pro Einheit
UnitPrice=Preis der Einheit
UnitsInStock=Lagerbestand
Discontinued=Eingestellt
Add the missing texts to the properties files.
Related Information
Element Binding [page 676]
Step 14: Expression Binding
Expression binding allows you to display a value on the screen that has been calculated from values found in some
model object. This way simple formatting or calculations can be inserted directly into the data binding string. In
this example, we will change the color of the price depending on whether it is above or below some arbitrary
threshold. The threshold value is also stored in the JSON model.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
261
Preview
Figure 62: Values formatted
Coding
You can view and download all files in the Demo Kit at Data Binding - Step 14.
webapp/view/App.view.xml
...
</content>
</Panel>
<Panel headerText="{i18n>panel3HeaderText}" class="sapUiResponsiveMargin"
width="auto">
<content>
<List headerText="{i18n>invoiceListTitle}" class="sapUiResponsiveMargin"
width="auto"
items="{/invoices}">
<items>
<ObjectListItem
262
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
title="{Quantity} x {ProductName}"
number="{ parts: [{path: 'ExtendedPrice'},
{path: '/currencyCode'}],
type: 'sap.ui.model.type.Currency',
formatOptions: { showMeasure: false }
}"
numberUnit="{/currencyCode}"
numberState="{= ${products>UnitPrice} > ${/priceThreshold} ?
'Error' : 'Success' }">
<attributes>
<ObjectAttribute text="{ path: 'Status',
formatter: '.formatStatusText' }"/>
</attributes>
</ObjectListItem>
</items>
</List>
</content>
</Panel>
</mvc:View>
In the XML view, we add a new numberState property to the ObjectListItem element within the List. The
value of this property is an expression that will be evaluated for each item.
webapp/index.html
...
"salesToDate" : 12345.6789,
"priceThreshold" : 20,
"currencyCode" : "EUR"
...
We add a new property called priceThreshold against which each invoice value will be checked.
As a result of binding an expression to the numberState property, the error status (color) of the price field will
change depending on the invoice value.
Look at the following two expressions:
● numberState="{= ${products>UnitPrice} > ${/priceThreshold} ? 'Error' :
'Success' }">
● numberState="{= ${products>UnitPrice} <= ${/priceThreshold} ? 'Success' :
'Error' }">
Can you see why one of these expressions will work, and the other will not?
Logically, both expressions are identical; yet the first one works, and the second does not: it produces only an
empty screen and an "Invalid XML" message in the browser's console… Hmmm, what's going on here?
In order to understand why this situation occurs, you must understand how XML files are parsed.
When an XML file is parsed, certain characters have a special (that is, high priority) meaning to the XML parser.
When such characters are encountered, they are always interpreted to be part of the XML definition itself and not
part of any other content that might exist within the XML document.
As soon as the XML parser encounters one of these high-priority characters (in this case, a less-than (<)
character), it will always be interpreted as the start of a new XML tag – irrespective of any other meaning that
character might have within the context of the expression. This is known as a syntax collision.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
263
In this case, the collision occurs between the syntax of XML and the syntax of the JavaScript-like expression
language used by OpenUI5.
Therefore, this statement fails because the less-than character is interpreted as the start of an XML tag:
numberState="{= ${products>UnitPrice} <= ${/priceThreshold} ? 'Success' : 'Error' }">
This particular problem can be avoided in one of two ways:
● Reverse the logic of the condition (use "greater than or equal to" instead of "less than")
● Use the escaped value for the less-than character: numberState="{= ${products>UnitPrice} &lt;= $
{/priceThreshold} ? 'Success' : 'Error' }">
Since the use of an escaped character is not so easy to read, the preferred approach is to reverse the logic of the
condition and use a greater-than character instead.
The ampersand (&) character also has a high priority meaning to the XML parser. This character will always be
interpreted to mean "The start of an escaped character". So if you wish to use the Boolean AND operator (&&) in a
condition, you must escape both ampersand characters (&amp;&amp;).
Related Information
Expression Binding [page 719]
264
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Step 15: Aggregation Binding Using a Factory Function
Instead of hard-coding a single template control, we use a factory function to generate different controls based on
the data received at runtime. This approach is much more flexible and allows complex or heterogeneous data to be
displayed.
Preview
Figure 63: Controls generated based on data
Coding
You can view and download all files in the Demo Kit at Data Binding - Step 15.
webapp/view/App.view.xml
...
<Panel headerText="{i18n>panel3HeaderText}" class="sapUiResponsiveMargin"
width="auto">
<content>
<List id="ProductList" headerText="{i18n>productListTitle}" items="{
path: 'products>/Products',
factory: '.productListFactory'
}" />
</content>
</Panel>
...
The List XML element that previously held the product list is now reduced simply to a named, but otherwise
empty placeholder. Without a factory function to populate it, this List would always remain empty.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
265
webapp/controller/App.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/type/Currency",
"sap/m/ObjectAttribute"
], function(Controller, Currency, ObjectAttribute) {
"use strict";
return Controller.extend("sap.ui.demo.db.controller.App", {
formatMail: function(sFirstName, sLastName) {
var oBundle = this.getView().getModel("i18n").getResourceBundle();
return sap.m.URLHelper.normalizeEmail(
sFirstName + "." + sLastName + "@example.com",
oBundle.getText("mailSubject", [sFirstName]),
oBundle.getText("mailBody"));
},
formatStockValue : function(fUnitPrice, iStockLevel, sCurrCode) {
var sBrowserLocale = sap.ui.getCore().getConfiguration().getLanguage();
var oLocale = new sap.ui.core.Locale(sBrowserLocale);
var oLocaleData = new sap.ui.core.LocaleData(oLocale);
var oCurrency = new Currency(oLocaleData.mData.currencyFormat);
return oCurrency.formatValue([fUnitPrice * iStockLevel, sCurrCode],
"string");
},
onItemSelected : function(oEvent) {
var oSelectedItem = oEvent.getSource();
var oContext = oSelectedItem.getBindingContext("products");
var sPath = oContext.getPath();
var oProductDetailPanel = this.byId("productDetailsPanel");
oProductDetailPanel.bindElement({ path: sPath, model: "products" });
},
productListFactory : function(sId, oContext) {
var oUIControl;
// Decide based on the data which fragment to show
if (oContext.getProperty("UnitsInStock") === 0 &&
oContext.getProperty("Discontinued")) {
// The item is discontinued, so use a StandardListItem
if (!this._oProductSimple) {
this._oProductSimple = sap.ui.xmlfragment(sId,
"sap.ui.demo.db.view.ProductSimple", this);
}
oUIControl = this._oProductSimple.clone();
} else {
// The item is available, so we will create an ObjectListItem
if (!this._oProductExtended) {
this._oProductExtended = sap.ui.xmlfragment(sId,
"sap.ui.demo.db.view.ProductExtended", this);
}
oUIControl = this._oProductExtended.clone();
}
});
266
});
}
// The item is temporarily out of stock, so we will add a status
if (oContext.getProperty("UnitsInStock") < 1) {
oUIControl.addAttribute(new ObjectAttribute({
text : {
path: "i18n>outOfStock"
}
}));
}
return oUIControl;
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
In the App controller, we create a new function called productListFactory. A factory function returns a control
for the associated binding context, similar to the XML templates we have defined in the previous steps. The types
of controls returned by this factory function must suit the items aggregation of the sap.m.List object. In this
case, we return either a StandardListItem or an ObjectListItem based on the data stored in the context of
the item to be created.
We decide which type of control to return by checking the current stock level and whether or not the product has
been discontinued. For both options, we prepare and load an XML fragment so that we can define the view logic
declaratively and assign the current controller. If the stock level is zero and the product has also been
discontinued, then we use the ProductSimple XML fragment, otherwise the ProductExtended XML fragment.
The XML fragments need to be loaded only once for each case, so we create a Singleton by storing a helper
variable on the controller and only loading it once. For each item of the list, we clone the corresponding control
stored on the controller. This creates a fresh copy of a control that we can bind to the context of the list item.
If the product is not discontinued but the stock level is zero, we are temporarily out of stock. In this case, we add a
single ObjectAttribute that adds the Out of Stock message to the control using JavaScript. Similar to
declarative definitions in the XML view or fragments, we can bind properties using data binding syntax. In this
case, we bind the text to a property in the resource bundle. Since the Attribute is a child of the list item, it has
access to all assigned models and the current binding context.
Finally, we return the control that is displayed inside the list.
webapp/view/ProductSimple.fragment.xml (new)
<core:FragmentDefinition
xmlns="sap.m"
xmlns:core="sap.ui.core">
<StandardListItem
icon="sap-icon://warning"
title="{products>ProductName} ({products>QuantityPerUnit})"
info="{i18n>Discontinued}"
type="Active"
infoState="Error"
press="onItemSelected">
</StandardListItem>
</core:FragmentDefinition>
The XML fragment defines a StandardListItem that is used if the stock level is zero and the product has also
been discontinued. This is our simple use case where we just define a warning icon and a Product Discontinued
message in the info property.
webapp/view/ProductExtended.fragment.xml (new)
<core:FragmentDefinition
xmlns="sap.m"
xmlns:core="sap.ui.core">
<ObjectListItem
title="{products>ProductName} ({products>QuantityPerUnit})"
number="{
parts: [
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
267
{path: 'products>UnitPrice'},
{path: '/currencyCode'}
],
type: 'sap.ui.model.type.Currency',
formatOptions : {
showMeasure : false
}
}"
type="Active"
numberUnit="{/currencyCode}"
press="onItemSelected">
</ObjectListItem>
</core:FragmentDefinition>
In our extended use case, we create an ObjectListItem to display more details of the product. The properties
are bound to the fields of the current data binding context and therefore can use types, formatters, and all
handlers that are defined in the assigned controller.
However, more complex logic can’t be defined declaratively in XML. Therefore, when the stock level is zero, we add
a single ObjectAttribute that displays the Out of Stock message in the controller using JavaScript.
webapp/i18n/i18n.properties
...
# Product Details
...
outOfStock=Out of Stock
webapp/i18n/i18n_de.properties
...
# Product Details
...
outOfStock=Nicht vorrätig
We add the missing texts to the properties files.
That's all - you completed the Data Binding tutorial!
Related Information
List Binding (Aggregation Binding) [page 680]
XML Fragments [page 734]
Using Factory Functions [page 682]
268
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Navigation and Routing
OpenUI5 comes with a powerful routing API that helps you control the state of your application efficiently. This
tutorial will illustrate all major features and APIs related to navigation and routing in OpenUI5 apps by creating a
simple and easy to understand mobile app. It represents a set of best practices for applying the navigation and
routing features of OpenUI5 to your applications.
In classical Web applications, the server determines which resource is requested based on the URL pattern of the
request and serves it accordingly. The server-side logic controls how the requested resource or page is displayed in
an appropriate way.
In single-page applications, only one page is initially requested from the server and additional resources are
dynamically loaded using client-side logic. The user only navigates within this page. The navigation is persisted in
the hash instead of the server path or URL parameters.
For example, a classical Web application might display the employee’s resume page when URL http://<yourhost>/<some-path-to-the-app>/employees/resume.html?id=3 or http://<your-host>/<somepath-to-the-app>/employees/3/resume is called. A single-page application instead would do the same thing
by using a hash-based URL like http://<your-host>/<some-path-to-the-app>/#/employees/3/resume.
The information in the hash, namely everything that is following the # character, is interpreted by the router.
Note
This tutorial does not handle cross-app navigation with the SAP Fiori launchpad. However, the concepts
described in this tutorial are also fundamental for navigation and routing between apps in the SAP Fiori
launchpad.
We will create a simple app displaying the data of a company’s employees to show typical navigation patterns and
routing features. The complete flow of the application can be seen in the figure below. We'll start with the home
page which lets users do the following:
● Display a Not Found page
● Navigate to a list of employees and drill further down to see a Details page for each employee
● Show an Employee Overview that they can search and sort
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
269
Figure 64: Page flow of the final app
Throughout this tutorial we will add features for navigating to pages and bookmarking them. We will add backward
and forward navigation with common transition animations (slide, show, flip, etc.). We will add more pages to the
app and navigate between them to show typical use cases. We will even learn how to implement features for
bookmarking a specific search, table sorting via filters, and dialogs.
Note
This tutorial is based on OpenUI5 version 1.30 or higher. The navigation features shown in this tutorial are
available since version 1.28. However, the configuration via the descriptor file manifest.json is only available
since 1.30.
Tip
You don't have to do all tutorial steps sequentially, you can also jump directly to any step you want. Just
download the code from the previous step, and start there.
You can view and download the files for all steps in the Demo Kit at Navigation and Routing. Copy the code to
your workspace and make sure that the application runs by calling the webapp/index.html file. Depending on
your development environment you might have to adjust resource paths and configuration entries.
For more information check the following sections of the tutorials overview page (see Get Started: Setup and
Tutorials [page 38]):
● Downloading Code for a Tutorial Step [page 40]
● Adapting Code to Your Development Environment [page 40]
270
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Step 1: Set Up the Initial App
We start by setting up a simple app for this tutorial. The app displays mock data only and mimics real OData backend calls with the mock server as you have seen in the Walkthrough tutorial.
The structure and data model created in this step will be used throughout the rest of this tutorial. The initial app
created in this step will be extended in the subsequent steps to illustrate the navigation and routing features of
OpenUI5.
Preview
Figure 65: Initial app with a simple button
Setup
To set up your project for this tutorial, download the files for Step 1 from the Samples in the Demo Kit at Navigation
and Routing - Step 1. Copy the code to your workspace and make sure that the application runs by calling the
webapp/index.html file.
Depending on your development environment you might have to adjust resource paths and configuration entries.
The project structure and the files coming with this tutorial are explained in detail in the Walkthrough [page 86]
tutorial.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
271
You should have the same files as displayed in the following figure:
Figure 66: Folder structure with downloaded files
Note
The content of the localService folders will not be changed in this tutorial. The i18n folder will always
contain the i18n.properties file only. Therefore, we will show both subfolders collapsed in the following
steps.
The Initial App
With the downloaded coding, you have an initial app with recommended settings that provides the basic features
of an OpenUI5 app:
● Home Page
The home page of our app is defined in the webapp/index.html file. In this file we bootstrap OpenUI5 and
tell the runtime where to find our custom resources. Furthermore, we initialize the MockServer to simulate
back-end requests as we do not have a real back-end service throughout this tutorial. Finally, we instantiate
the application component, assign it to a sap.m.Shell control, and place the shell into the body. The
corresponding Component.js file in the webapp folder will be extended throughout this tutorial.
● Data
In the webapp/localService/mockserver.js file, we configure the mock server. Using the mock server in
this tutorial allows us to easily run the code even without network connection and without the need of having a
remote server for our application data.
272
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
The metadata.xml file used by the mock server describes our OData service. The service only has two OData
entities:
○ Employee
An employee has typical properties like FirstName and LastName as well as a navigation property to a
resume entity referenced by a ResumeID. Of course, the entity also has an ID property: EmployeeID. The
corresponding EntitySet is Employees. The actual test data containing several employees is located in
the webapp/localService/mockdata/Employees.json file.
○ Resume
In our case, we want to keep the resume of employees very simple. Therefore, we just have simple
properties of type Edm.String. The properties are Information, Projects, Hobbies and Notes; all of
them contain textual information. The entity has an ID property ResumeID and the corresponding
EntitySet is Resumes. The resume data for an employee is located in file webapp/localService/
mockdata/Resumes.json.
● Configuration of the App
In the webapp/manifest.json descriptor file, we configure our app. The descriptor file contains the
following most interesting sections:
○ sap.app
In this section we reference an i18n.properties file and use a special syntax to bind the texts for the
title and description properties.
In the dataSources part, we tell our app where to find our OData service employeeRemote. As you
might guess, the uri correlates to the rootUri of our mock server instance which can be found in
webapp/localService/mockserver.js. It is important that these two paths match to allow our mock
server to provide the test data we defined above. The localUri is used to determine the location of the
metadata.xml file.
○ sap.ui5
Under sap.ui5 we declare with the rootView parameter that our sap.ui.demo.nav.view.App view
shall be loaded and used as the rootView for our app. Furthermore, we define two models to be
automatically instantiated and bound to the i18n component and a default model "". The latter
references our employeeRemote dataSource which is declared in our sap.app section as an OData 2.0
data source. The i18n file can be found at webapp/i18n/i18n.properties. This data source will be
mocked by our mock server.
So far we have a basic app that does not really have any navigation or routing implemented. This will change in the
next steps when we implement our first navigation features.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
273
Step 2: Enable Routing
In this step we will modify the app and introduce routing. Instead of having the home page of the app hard coded
we will configure a router to wire multiple views together when our app is called. The routing configuration controls
the application flow when the user triggers a navigation action or opens a link to the application directly.
Preview
Figure 67: Views are wired together using the router
Coding
You can view and download all files in the Samples in the Demo Kit at Routing and Navigation - Step 2.
Figure 68: Folder structure for this step
webapp/manifest.json
{
274
"_version": "1.8.0",
"sap.app": {
...
},
"sap.ui": {
...
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
}
},
"sap.ui5": {
"rootView": {
"viewName": "sap.ui.demo.nav.view.App",
"type": "XML",
"async": true,
"id": "app"
},
"dependencies": {
...
},
"models": {
...
},
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "sap.ui.demo.nav.view",
"controlId": "app",
"controlAggregation": "pages",
"transition": "slide"
"async": true
},
"routes": [{
"pattern": "",
"name": "appHome",
"target": "home"
}],
"targets": {
"home": {
"viewId": "home",
"viewName": "Home",
"viewLevel" : 1
}
}
}
}
Single-page applications based on OpenUI5 can use a so-called “router” to dispatch hash-based URLs to one or
more views of the app. Therefore, the router needs to know how to address and show the views. In OpenUI5, we
can simply add a routing section to our existing sap.ui5 section in the descriptor file to configure the router.
There are three properties that can be used to configure the routing of your application:
● config
This section contains the global router configuration and default values that apply for all routes and targets.
The default value for routerClass is sap.ui.core.routing.Router. We set the routerClass to
sap.m.routing.Router because we implement an app based on sap.m. Furthermore, we define where our
views are located in the app. To load and display views automatically, we also specify the controlId of the
control that is used to display the pages and the aggregation (controlAggregation) that will be filled when
a new page is displayed. We will create only XMLviews in this tutorial, so we can set the viewType property to
XML. All our views will be available in the view folder of the namespace sap.ui.demo.nav, so we can set the
viewPath to sap.ui.demo.nav.view. The transition allows us to set a default value for how the
transition should happen; you can choose between slide (default), flip, fade, and show. All parameters of
the config section can be overruled in the individual route and target definitions if needed.
Note
The possible values for routerClass are sap.ui.core.routing.Router, sap.m.routing.Router, or
any other subclasses of sap.ui.core.routing.Router. Compared to
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
275
sap.ui.core.routing.Router the sap.m.routing.Router is optimized for mobile apps and adds the
properties viewLevel, transition and transitionParameters which can be specified for each route
or target created by the sap.m.routing.Router. The transitionParameters can also be used for
custom transitions. Please check the API Reference for more information.
● routes
Each route defines a name, a pattern, and one or more targets to navigate to when the route has been hit. The
pattern is basically the hash part of the URL that matches the route. The sequence of the routes is important
because only the first matched route is used by the router. In our case, we have an empty pattern to match the
empty hash. The name property allows you to choose a unique route name that helps you to navigate a specific
route or to determine the matched route in one of the matched handlers (we'll explain that in a later step). The
target property references one or more targets from the section below that will be displayed when the route
has been matched.
● targets
A target defines the view that is displayed. It is associated with one or more routes or it can be displayed
manually from within the app. Whenever a target is displayed, the corresponding view is loaded and added to
the aggregation configured with the controlAggregation option of the control. This option is configured
using controlId. Each target has a unique key (home). The viewName defines which view shall be loaded. In
our little example, the absolute view path to be loaded for our home target is determined by the default
"viewPath": "sap.ui.demo.nav.view" and "viewName": "Home". This leads to
"sap.ui.demo.nav.view.Home". The viewLevel is especially relevant for flip and slide transitions. It
helps the router to determine the direction of the transition from one page to another. (This will also be
explained later.) A target can be assigned to a route, but it's not necessary. Targets can be displayed directly in
the app without hitting a route.
This basic routing configuration was easy enough. However, you can’t see it in action until you have initialized
the router.
Note
As of OpenUI5 version 1.30, we recommend that you define the routing in the manifest.json descriptor file
using routes and targets. In older versions of OpenUI5, the routing configuration had to be done directly in the
metadata section of the component, and with different syntax.
webapp/Component.js
sap.ui.define([
"sap/ui/core/UIComponent"
], function (UIComponent) {
"use strict";
return UIComponent.extend("sap.ui.demo.nav.Component", {
metadata: {
manifest: "json"
},
init: function () {
// call the init function of the parent
UIComponent.prototype.init.apply(this, arguments);
}
276
// create the views based on the url/hash
this.getRouter().initialize();
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
});
});
We override the init function and call the parent’s init function first. We get a reference to the router and call
initialize() on it. The router is instantiated automatically with the configuration loaded in the descriptor. The
routing events and our configuration in the descriptor are now automatically enabled in the app. Running the app
at this point would lead to an error, because the home view is not implemented yet.
webapp/view/App.view.xml
<mvc:View
controllerName="sap.ui.demo.nav.controller.App"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
displayBlock="true">
<App id="app"/>
</mvc:View>
In the App view, we remove the content of App control. The pages will be added dynamically the way we have
configured it in the descriptor. The view configured with the property rootView is automatically instantiated when
the app is called initially.
webapp/view/Home.view.xml (New)
<mvc:View
controllerName="sap.ui.demo.nav.controller.Home"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Page title="{i18n>homePageTitle}" class="sapUiResponsiveContentPadding">
<content>
<Button text="{i18n>iWantToNavigate}" class="sapUiTinyMarginEnd"/>
</content>
</Page>
</mvc:View>
Create a file Home.view.xml in the webapp/view folder. The home view only contains a page control that
displays a button. For illustration, we bind the title of the page to the i18n>homePageTitle, you can use data
binding just the way you are used to it.
webapp/controller/Home.controller.js (New)
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function (Controller) {
"use strict";
return Controller.extend("sap.ui.demo.nav.controller.Home", {
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
277
});
});
Create a file Home.controller.js in the webapp/controller folder. The controller for the home view does not
contain any custom logic in this step, but we will add some features to it soon. Finally, run the app by calling the
webapp/index.html file. This will be the entry point for our app in all the next steps. As you can see, the app is
initially displaying the home view that we configured as the default pattern in the routing configuration. We have
now successfully enabled routing in the app.
Note
We think of routing as a set of features that dispatch hash-based URLs to an app's views and manage the views'
states.
Based on the routing configuration, you define the navigation between pages and pass parameters to the target
views.
Conventions
● Configure the router in the manifest.json descriptor file
● Initialize the router exactly once
● Initialize the router in the component
Related Information
Routing and Navigation [page 797]
API Reference: sap.ui.core.routing
API Reference: sap.ui.core.routing.Route
API Reference: sap.ui.core.routing.Route: Constructor Detail
API Reference: sap.m.routing.Router
Step 3: Catch Invalid Hashes
Sometimes it is important to display an indication that the requested resource was not found. To give you an
example: If a user tries to access an invalid pattern which does not match any of the configured routes, the user is
notified that something went wrong. You might also know this as a “404” or Not Found Page from traditional web
pages. In this step, we will implement a feature that detects invalid hashes and visualizes this in a nice way.
278
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Preview
Figure 69: Not Found page
Coding
You can view and download all files in the Samples in the Demo Kit at Routing and Navigation - Step 3.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
279
Figure 70: Folder structure for this step
webapp/manifest.json
{
}
280
...
"sap.ui5": {
...
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "sap.ui.demo.nav.view",
"controlId": "app",
"controlAggregation": "pages",
"transition": "slide",
"bypassed": {
"target": "notFound"
}
},
"routes": [{
"pattern": "",
"name": "appHome",
"target": "home"
}],
"targets": {
"home": {
"viewName": "home"
"viewName": "Home",
"viewLevel" : 1
},
"notFound": {
"viewName": "notFound"
"viewName": "NotFound",
"transition": "show"
}
}
}
}
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Let’s extend the routing configuration in the descriptor by adding a bypassed property and setting its target to
notFound. This configuration tells the router to display the notFound target in case no route was matched to the
current hash. Next, we add a notFound target to the bypassed section. The notFound target simply configures a
notFound view with a show transition.
webapp/view/NotFound.view.xml (New)
<mvc:View
controllerName="sap.ui.demo.nav.controller.NotFound"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<MessagePage
title="{i18n>NotFound}"
text="{i18n>NotFound.text}"
description="{i18n>NotFound.description}"/>
</mvc:View>
Now we create the view referenced above in a new file NotFound.view.xml in the webapp/view folder. It uses
a sap.m.MessagePage control to display an error message to the user. In a real app you might use a dynamic
message matchint the current error situation. Here, we simply display a preconfigured text from our resource
bundle.
webapp/controller/NotFound.controller.js (New)
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function (Controller) {
"use strict";
return Controller.extend("sap.ui.demo.nav.controller.NotFound", {
onInit: function () {
}
});
});
Now we create the controller for the NotFound view and save it into the webapp/controller folder. This
controller will be extended later.
webapp/i18n/i18n.properties
...
NotFound=Not Found
NotFound.text=Sorry, but the requested resource is not available.
NotFound.description=Please check the URL and try again.
Add the new properties to the i18n.properties file.
Open the URL index.html#/thisIsInvalid in your browser. From now on the user will see a nice Not Found
page if a hash could not be matched to one of our routes.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
281
Conventions
● Always configure the bypassed property and a corresponding target
● Use the sap.m.MessagePage control to display routing related error messages
Related Information
API Reference: sap.m.MessagePage
API Overview and Samples: sap.m.MessagePage
Step 4: Add a Back Button to Not Found Page
When we are on the Not Found page because of an invalid hash, we want to get back to our app to select another
page. Therefore, we will add a Back button to the Not Found view and make sure that the user gets redirected to
either the previous page or the overview page when the Back button is pressed.
Preview
Figure 71: Not Found page with Back button
282
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Coding
You can view and download all files in the Samples in the Demo Kit at Routing and Navigation - Step 4.
webapp/view/NotFound.view.xml
<mvc:View
controllerName="sap.ui.demo.nav.controller.NotFound"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<MessagePage
title="{i18n>NotFound}"
text="{i18n>NotFound.text}"
description="{i18n>NotFound.description}"
showNavButton="true"
navButtonPress="onNavBack"/>
</mvc:View>
In the NotFound view, we set the property showNavButton of the MessagePage control to true to automatically
display the Back button. We also add an event handler function onNavBack to the navButtonPress event of the
control. The onNavBack function will handle the actual back navigation. We could directly add this function to the
view’s controller. However, we are smart enough to anticipate that we might need the same handler function for
different views. DRY (“Don’t Repeat Yourself”) is the right approach for us, so let’s create a BaseController from
which all other controllers will inherit.
webapp/controller/BaseController.js (New)
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/core/routing/History"
], function (Controller, History) {
"use strict";
return Controller.extend("sap.ui.demo.nav.controller.BaseController", {
getRouter : function () {
return sap.ui.core.UIComponent.getRouterFor(this);
},
onNavBack: function (oEvent) {
var oHistory, sPreviousHash;
oHistory = History.getInstance();
sPreviousHash = oHistory.getPreviousHash();
if (sPreviousHash !== undefined) {
window.history.go(-1);
} else {
this.getRouter().navTo("appHome", {}, true /*no history*/);
}
}
});
});
Create a new BaseController.js file in the webapp/controller folder. The base controller implements a set
of functions that are reused by its subclasses. The onNavBack handler is a great example of code that we don’t
want to duplicate in our controllers for each page that has a back navigation.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
283
The function checks if there is a previous hash value in the app history. If so, it redirects to the previous hash via
the browser’s native History API. In case there is no previous hash we simply use the router to navigate to the
route appHome which is our home view.
The third parameter of navTo("appHome", {}, true /*no history*/); has the value true and makes sure
that the hash is replaced. With the line sap.ui.core.UIComponent.getRouterFor(this) you can easily
access your component’s router throughout the app. To make it even more comfortable, we also add a handy
shortcut getRouter to the base controller. This function is now available in each subclass as well. It is also used in
the onNavBack handler to get a reference to the router before calling navTo. We now have to implement the reuse
in all other controllers.
Note
In OpenUI5 there are multiple options to reuse code. We recommend to use a base controller for such helper
methods because this allows us to decoratively use the onNavBack handler directly in any XML view without
adding additional code to the controller. Our base controller is an abstract controller that will not be instantiated
in any view. Therefore, the naming convention *.controller.js does not apply, and we can just call the file
BaseController.js. By not using the naming convention *.controller.js we can even prevent any usage
in views.
webapp/controller/NotFound.controller.js
sap.ui.define([
"sap/ui/demo/nav/controller/BaseController"
], function (BaseController) {
"use strict";
return BaseController.extend("sap.ui.demo.nav.controller.NotFound", {
onInit: function () {
}
});
});
In order to reuse the base controller implementation, we have to change the dependency from sap/ui/
core/mvc/Controller to sap/ui/demo/nav/controller/BaseController and directly extend the base
controller.
At this point you can open index.html#/thisIsInvalid in your browser and press the Back button to see what
happens. You will be redirected to the app’s home page that is matched by the route appHome as you opened the
Not Found page with an invalid hash. If you change the hash to something invalid when you are on the home page
of the app, you will also go to the Not Found page but with a history entry. When you press back, you will get to the
home page again, but this time with a native history navigation.
webapp/controller/App.controller.js
sap.ui.define([
"sap/ui/demo/nav/controller/BaseController"
], function (BaseController) {
"use strict";
284
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
});
return BaseController.extend("sap.ui.demo.nav.controller.App", {
onInit: function () {
}
});
To be consistent, we will now extend all of our controllers with the base controller. Change the app controller as
described above.
webapp/controller/Home.controller.js
sap.ui.define([
"sap/ui/demo/nav/controller/BaseController"
], function (BaseController) {
"use strict";
return BaseController.extend("sap.ui.demo.nav.controller.Home", {
});
});
The same applies to our home controller, we also extend it with the base controller now.
Note
In this step we have added the Back button. The user can always use the browser’s native Back button as well.
Each app can freely configure the behavior of the Back button. However, there is no clean way to apply the same
logic for the browser’s Back button in single-page applications. Tweaking the browser history or using other
quirks for cancelling backward or forward navigation is not recommended due to the implementation details of
the browsers. The browser’s Back button always uses the browser history while the Back button of the app can
make use of the browser history or can implement its own navigation logic. Make sure to understand this
difference and only control the Back button inside the app.
Conventions
● Implement a global onNavBack handler for back navigation in your app
● Query the history and go to the home page if there is no history available for the current app
Related Information
Routing and Navigation [page 797]
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
285
Step 5: Display a Target Without Changing the Hash
In this step, you will learn more about targets and how to display a target from the routing configuration manually.
We will display the Not Found target from the previous step without changing the hash to illustrate this navigation
pattern. We will also consider a side-effect that prevents us from navigating back in this case.
Fortunately, we can extend our app and offer an easy solution. There are some use cases that should not be
persisted in the URL but just be triggered by the application logic if needed. A target is a navigation-related
configuration for a view and we can display targets manually without referencing them in a navigation route. Good
examples for this are temporary errors, switching to an edit page for a business object, or going to a Settings page.
Sometimes you will also have to implement a way back manually.
Preview
Figure 72: The new Home page with a navigation button
Coding
You can view and download all files in the Samples in the Demo -kit at Routing and Navigation - Step 5.
webapp/view/Home.view.xml
<mvc:View
controllerName="sap.ui.demo.nav.controller.Home"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Page title="{i18n>homePageTitle}" class="sapUiResponsiveContentPadding">
<content>
<Button id="displayNotFoundBtn" text="{i18n>DisplayNotFound}"
press="onDisplayNotFound" class="sapUiTinyMarginEnd"/>
</content>
</Page>
</mvc:View>
We start by changing the Button control from the home view. When the button is pressed the
onDisplayNotFound handler is called.
286
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/controller/Home.controller.js
sap.ui.define([
"sap/ui/demo/nav/controller/BaseController"
], function (BaseController) {
"use strict";
return BaseController.extend("sap.ui.demo.nav.controller.Home", {
onDisplayNotFound : function (oEvent) {
//display the "notFound" target without changing the hash
this.getRouter().getTargets().display("notFound");
}
});
});
Inside the onDisplayNotFound handler we get a reference to the Targets helper object of the router and simply
call display("notFound"). The view associated to the target with the name notFound from the routing
configuration will be displayed by the router without changing the hash.
The sap.m.routing.Targets object itself can be retrieved by calling getTargets() on the router. It provides a
convenient way for placing views into the correct containers of your application. The main benefits of targets are
structuring and lazy loading: you just configure the views in the routing configuration and you do not have to load
the views until you really need them.
Note
In the example code we get a reference to the sap.m.routing.Targets object by calling getTargets() on
this.getRouter() from the base controller. However, you could also get a reference to the
sap.m.routing.Targets object by calling this.getOwnerComponent().getRouter().getTargets()
or this.getOwnerComponent().getTargets().
If you now call the app and press the Display Not Found button you see that the notFound target is displayed
without changing the URL. That was easy, but suddenly our app’s Back button does not work anymore. The bug we
have just introduced illustrates an interesting navigation trap. The application hash is still empty since we just
display the target and did not hit a route.
When pressing the app’s Back button, the onNavBack from the previous step is called. It detects that there is no
previous hash and therefore tries to navigate to the appHome route again. The router is smart enough to detect
that the current hash did not change and therefore skips the navigation to the route. Fortunately, there is an easy
workaround for us. However, we need to touch the Home controller again.
webapp/controller/Home.controller.js (Changed Again)
sap.ui.define([
"sap/ui/demo/nav/controller/BaseController"
], function (BaseController) {
"use strict";
return BaseController.extend("sap.ui.demo.nav.controller.Home", {
onDisplayNotFound : function (oEvent) {
//display the "notFound" target without changing the hash
this.getRouter().getTargets().display("notFound", {
fromTarget : "home"
});
}
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
287
});
});
This time we pass on a data object as the second parameter for the display method which contains the name of
the current target; the one from which we navigate to the notFound target. We decide to choose the key
fromTarget but since it is a custom configuration object any other key would be fine as well.
webapp/controller/NotFound.controller.js
sap.ui.define([
"sap/ui/demo/nav/controller/BaseController"
], function (BaseController) {
"use strict";
return BaseController.extend("sap.ui.demo.nav.controller.NotFound", {
onInit: function () {
var oRouter, oTarget;
oRouter = this.getRouter();
oTarget = oRouter.getTarget("notFound");
oTarget.attachDisplay(function (oEvent) {
this._oData = oEvent.getParameter("data"); //store the data
}, this);
},
// override the parent's onNavBack (inherited from BaseController)
onNavBack : function (oEvent){
var oHistory, sPreviousHash, oRouter;
// in some cases we could display a certain target when the back button
is pressed
if (this._oData && this._oData.fromTarget) {
this.getRouter().getTargets().display(this._oData.fromTarget);
delete this._oData.fromTarget;
return;
}
// call the parent's onNavBack
BaseController.prototype.onNavBack.apply(this, arguments);
}
});
});
Next, we have to register an event listener to the display event of the notFound target. The best place for us to
register an event listener for this is inside the init function of our NotFound controller. There we can access and
store the custom data that we are passing on when displaying the target manually.
From the router reference we can fetch a reference to the notFound target. Each target configuration will create a
runtime object that can be accessed through the router.
Similar to OpenUI5 controls, targets define API methods and events that can be attached. We attach a display
event handler and save the data that was received as the event parameter data in an internal controller variable
_oData. This data also includes the fromTarget information in case the caller passed it on. However, we now
have to override the base controller’s onNavBack implementation to change the behavior a bit. We add a special
case for our target back functionality in case the fromTarget property has been passed on. If specified, we simply
display the target defined as fromTarget manually the same way we actually called the notFound target
manually. Otherwise we just call the base controller’s onNavBack implementation.
288
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/i18n/i18n.properties
...
DisplayNotFound=Display Not Found
Add the new property to the i18n.properties file.
When we now click the Back button, it works as expected and brings us back to the overview page, also when the
Not Found view is displayed manually.
Conventions
● Display targets manually if you want to trigger a navigation without changing the hash
● Think carefully about all navigation patterns in your application, otherwise the user might get stuck
Related Information
API Reference: sap.m.routing.Targets
API Reference: sap.ui.core.routing.Targets
API Reference: sap.ui.core.routing.Target
Step 6: Navigate to Routes with Hard-Coded Patterns
In this step, we'll create a second button on the home page, with which we can navigate to a simple list of
employees. This example illustrates how to navigate to a route that has a hard-coded pattern.
Preview
Figure 73: Show Employee List button on the Home page
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
289
Figure 74: Employee list with Back button
Coding
You can view and download all files in the Samples in the Demo Kit at Routing and Navigation - Step 6.
Figure 75: Folder structure for this step
290
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/view/Home.view.xml
<mvc:View
controllerName="sap.ui.demo.nav.controller.Home"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Page title="{i18n>homePageTitle}" class="sapUiResponsiveContentPadding">
<content>
<Button id="displayNotFoundBtn" text="{i18n>DisplayNotFound}"
press="onDisplayNotFound" class="sapUiTinyMarginEnd"/>
<Button id="employeeListBtn" text="{i18n>ShowEmployeeList}"
press="onNavToEmployees" class="sapUiTinyMarginEnd"/>
</content>
</Page>
</mvc:View>
First, we change the Home view by adding the Show Employee List button. We register an event handler
onNavToEmployees for the press event.
webapp/controller/Home.controller.js
sap.ui.define([
"sap/ui/demo/nav/controller/BaseController"
], function (BaseController) {
"use strict";
return BaseController.extend("sap.ui.demo.nav.controller.Home", {
onDisplayNotFound : function (oEvent) {
// display the "notFound" target without changing the hash
this.getRouter().getTargets().display("notFound", {
fromTarget : "home"
});
},
onNavToEmployees : function (oEvent){
this.getRouter().navTo("employeeList");
}
});
});
The new event handler onNavToEmployees calls navTo("employeeList") on the router instance. The
parameter employeeList is the name of the route that we want to navigate to.
webapp/manifest.json
{
"_version": "1.8.0",
"sap.app": {
...
},
"sap.ui": {
...
},
"sap.ui5": {
...
"routing": {
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
291
}
}
}
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "sap.ui.demo.nav.view",
"controlId": "app",
"controlAggregation": "pages",
"transition": "slide",
"bypassed": {
"target": "notFound"
}
},
"routes": [{
"pattern": "",
"name": "appHome",
"target": "home"
}, {
"pattern": "employees",
"name": "employeeList",
"target": "employees"
}],
"targets": {
"home": {
"viewId": "home",
"viewName": "Home",
"viewLevel" : 1
},
"notFound": {
"viewId": "notFound",
"viewName": "NotFound",
"transition": "show"
},
"employees": {
"viewId": "employeeList",
"viewPath": "sap.ui.demo.nav.view.employee",
"viewName": "EmployeeList",
"viewLevel" : 2
}
}
To make the navigation work, we have to extend the routing configuration of the app in the descriptor file. We add a
new pattern called employeeList; this is the name we used in the controller to trigger the navigation.
The pattern of the route is the hard-coded value employees, meaning the matching hash for this route is /#/
employees in the address bar of the browser. The target employees should be displayed when this URL pattern is
matched.
The employees entry in the targets section references the
sap.ui.demo.nav.view.employee.EmployeeList view. As you can see, we added a new namespace
employee for all views related to employees with the property viewPath. This overrides the default settings in the
config section for the current target.
The view that we are about to create has to be placed in the webapp/view/employee folder accordingly. This
approach helps to structure the views of the app according to business objects and to better understand the
navigation patterns of the app in larger projects.
Note
We could also have left out the viewPath property to use the default viewPath defined in the config section.
In that case, we would have to change the viewName to employee.EmployeeList to achieve the same effect.
292
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Setting the viewLevel to 2 helps the router to determine how to animate the (in our case) slide transition. For
us, this means that a navigation from the home page to the employees target will be animated with a “Slide to
Left” animation. In contrast to that, the back navigation from the employees target to the home page will be
animated with a “Slide to Right” animation. This behavior is due to the fact that the home page has a lower
viewLevel than the employees target.
webapp/view/employee/EmployeeList.view.xml (New)
<mvc:View
controllerName="sap.ui.demo.nav.controller.employee.EmployeeList"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Page id="employeeListPage" title="{i18n>EmployeeList}"
showNavButton="true"
navButtonPress="onNavBack"
class="sapUiResponsiveContentPadding">
<content>
<List id="employeeList" headerText="{i18n>ListOfAllEmployees}" items="{/
Employees}">
<items>
<StandardListItem
title="{FirstName} {LastName}"
iconDensityAware="false"
iconInset="false"/>
</items>
</List>
</content>
</Page>
</mvc:View>
We now create a subfolder employee below webapp/view and a file EmployeeList.view.xml.
We name the folder after the business object, to make it obvious from looking at the hash (included in the
browser's address bar) where a view file for a certain business object is located. For example, we can determine
from the URL /#/employee that the corresponding view must be somewhere in the folder ./employee (in our
case: webapp/view/employee) just by looking at the URL.
In the view, we use a sap.m.List control and bind its items to the data from our simulated OData service. Note
that we have also registered the onNavBack handler from the base controller again to be able to navigate back to
the overview.
This view can be referenced by sap.ui.demo.nav.view.employee.EmployeeList.
webapp/controller/employee/EmployeeList.controller.js (New)
sap.ui.define([
"sap/ui/demo/nav/controller/BaseController"
], function (BaseController) {
"use strict";
return
BaseController.extend("sap.ui.demo.nav.controller.employee.EmployeeList", {
});
});
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
293
Finally, we will add a new controller. Create a subfolder employee inside webapp/controller folder and place
the file EmployeeList.controller.js there. As you can see, the folder structure of the controllers is in sync
with the folder structure of the views.
webapp/i18n/i18n.properties
...
ShowEmployeeList=Show Employee List
EmployeeList=Employee List
ListOfAllEmployees=List of all employees
Add the new texts to the i18n.properties file.
Now you can open the app and press the Show Employee List button to navigate to the employee list. From there,
you can press either the browser’s or the app’s Back button to get back to the home page.
Related Information
Methods and Events for Navigation [page 803]
API Reference: sap.ui.core.routing.Route
Step 7: Navigate to Routes with Mandatory Parameters
In this step, we implement a feature that allows the user to click on an employee in the list to see additional details
of the employee. A route pattern can have one or more mandatory parameters to identify objects in an app.
The detail page has to read the ID of the employee from the URL to fetch and display the employee data from the
server. If the employee was not found, for example, because an invalid employee ID was passed on, we want to
inform the user by displaying the notFound target. Of course, the back navigation has to work as well for this page.
294
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Preview
Figure 76: Employee list with navigation option for items
Figure 77: Detail Page for a selected employee
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
295
Figure 78: Not Found page for an invalid EmployeeID
Coding
You can view and download all files in the Samples in the Demo Kit at Routing and Navigation - Step 7.
296
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Figure 79: Folder structure for this step
webapp/manifest.json
{
"_version": "1.8.0",
"sap.app": {
...
},
"sap.ui": {
...
},
"sap.ui5": {
...
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "sap.ui.demo.nav.view",
"controlId": "app",
"controlAggregation": "pages",
"transition": "slide",
"bypassed": {
"target": "notFound"
}
},
"routes": [{
"pattern": "",
"name": "appHome",
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
297
}
}
}
"target": "home"
}, {
"pattern": "employees",
"name": "employeeList",
"target": "employees"
}, {
"pattern": "employees/{employeeId}",
"name": "employee",
"target": "employee"
}],
"targets": {
"home": {
"viewId": "home",
"viewName": "Home",
"viewLevel" : 1
},
"notFound": {
"viewId": "notFound",
"viewName": "NotFound",
"transition": "show"
},
"employees": {
"viewId": "employeeList",
"viewPath": "sap.ui.demo.nav.view.employee",
"viewName": "EmployeeList",
"viewLevel" : 2
},
"employee": {
"viewId": "employee",
"viewName": "employee.Employee",
"viewLevel" : 3
}
}
From our data model (webapp/localService/metadata.xml or webapp/localService/mockdata/
Employees.json), you can see that each employee entity is identified by an EmployeeID. We define a new route
that expects a mandatory employeeId in its pattern to address an employee. Unlike the patterns we used before,
this pattern has a dynamic part. We create a new route employee and use employees/{employeeId} as its
pattern.
The {employeeId} part of the pattern is a mandatory parameter as indicated by the curly brackets. The hash
that contains an actual employee ID is matched against that pattern at runtime.
The following hashes would match in our case: employees/2, employees/7, employees/anInvalidId, and so
on. However, the hash employees/ will not match as it does not contain an ID at all. The target of our route is
employee. We create the target employee with viewLevel 3. With that, we make sure that we have the correct
slide animation direction.
Next, we have to create the view employees.Employee; for better illustration the viewPath is not specified this
time.
webapp/view/employee/Employee.view.xml (New)
<mvc:View
controllerName="sap.ui.demo.nav.controller.employee.Employee"
xmlns="sap.m"
298
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
xmlns:mvc="sap.ui.core.mvc"
xmlns:f="sap.ui.layout.form"
busyIndicatorDelay="0">
<Page
id="employeePage"
title="{i18n>EmployeeDetailsOf} {FirstName} {LastName}"
showNavButton="true"
navButtonPress="onNavBack"
class="sapUiResponsiveContentPadding">
<content>
<Panel
id="employeePanel"
width="auto"
class="sapUiResponsiveMargin sapUiNoContentPadding">
<headerToolbar>
<Toolbar>
<Title text="{i18n>EmployeeIDColon} {EmployeeID}"
level="H2"/>
<ToolbarSpacer />
</Toolbar>
</headerToolbar>
<content>
<f:SimpleForm
minWidth="1024"
editable="false"
layout="ResponsiveGridLayout"
labelSpanL="3" labelSpanM="3" emptySpanL="4" emptySpanM="4"
columnsL="1" columnsM="1">
<f:content>
<Label text="{i18n>FirstName}" />
<Text text="{FirstName}" />
<Label text="{i18n>LastName}" />
<Text text="{LastName}" />
<Label text="{i18n>Address}" />
<Text text="{Address}" />
<Label text="{i18n>City}" />
<Text text="{City}, {Region}" />
<Label text="{i18n>PostalCode}" />
<Text text="{PostalCode}" />
<Label text="{i18n>PhoneHome}" />
<Text text="{HomePhone}" />
<Label text="{i18n>Country}" />
<Text text="{Country}" />
</f:content>
</f:SimpleForm>
</content>
</Panel>
</content>
</Page>
</mvc:View>
Create the file Employee.view.xml inside the webapp/view/employee folder. This employee view displays
master data for an employee in a panel with a SimpleForm control: first name, last name and so on. The data
comes from a relative data binding that is set on the view level as we can see in the controller later. As we are
focusing on the navigation aspects in this tutorial, we won’t go into detail on the controls of the view. Just copy the
code.
webapp/controller/employee/Employee.controller.js (New)
sap.ui.define([
"sap/ui/demo/nav/controller/BaseController"
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
299
], function (BaseController) {
"use strict";
return BaseController.extend("sap.ui.demo.nav.controller.employee.Employee", {
onInit: function () {
var oRouter = this.getRouter();
oRouter.getRoute("employee").attachMatched(this._onRouteMatched, this);
// Hint: we don't want to do it this way
/*
oRouter.attachRouteMatched(function (oEvent){
var sRouteName, oArgs, oView;
sRouteName = oEvent.getParameter("name");
if (sRouteName === "employee"){
this._onRouteMatched(oEvent);
}
}, this);
*/
},
_onRouteMatched : function (oEvent) {
var oArgs, oView;
oArgs = oEvent.getParameter("arguments");
oView = this.getView();
oView.bindElement({
path : "/Employees(" + oArgs.employeeId + ")",
events : {
change: this._onBindingChange.bind(this),
dataRequested: function (oEvent) {
oView.setBusy(true);
},
dataReceived: function (oEvent) {
oView.setBusy(false);
}
}
});
});
});
},
_onBindingChange : function (oEvent) {
// No data for the binding
if (!this.getView().getBindingContext()) {
this.getRouter().getTargets().display("notFound");
}
}
Now we create the file Employee.controller.js in the webapp/controller/employee folder. In this
controller file, we want to detect which employee shall be displayed in order to show the employee’s data in the
view. Therefore, we query the router for the route employee and attach a private event listener function
_onRouteMatched to the matched event of this route.
In the event handler, we can access the arguments parameter from the oEvent parameter that contains all
parameters of the pattern. Since this listener is only called when the route is matched, we can be sure that the
mandatory parameter employeeId is always available as a key in arguments; otherwise the route would not have
matched. The name of the mandatory parameter employeeId correlates to the {employeeId} from our pattern
definition of the route employee and thus to the value in the URL.
In _onRouteMatched we call bindElement() on the view to make sure that the data of the specified employee is
available in the view and it’s controls. The ODataModel will handle the necessary data requests to the back end in
the background. While the data is loading, it would be nice to show a busy indicator by simply setting the view to
busy. Therefore, we pass an events object to bindElement() to listen to the events dataRequested and
dataReceived. The attached functions handle the busy state by calling oView.setBusy(true) and
oView.setBusy(false) respectively.
300
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
We also add an event handler to the change event as a private function _onBindingChange. It checks if the data
could be loaded by querying the binding context of the view. As seen in the previous steps, we will display the
notFound target if the data could not be loaded.
Note
Instead of calling attachMatched(…) on a route we could also call attachRouteMatched(…) directly on the
router. However, the event for the latter is fired for every matched event of any route in the whole app. We don’t
use the latter because we would have to implement an additional check for making sure that current route is the
route that has been matched. We want to avoid this extra overhead and register on the route instead.
webapp/view/employee/EmployeeList.view.xml
<mvc:View
controllerName="sap.ui.demo.nav.controller.employee.EmployeeList"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Page id="employeeListPage" title="{i18n>EmployeeList}"
showNavButton="true"
navButtonPress="onNavBack"
class="sapUiResponsiveContentPadding">
<content>
<List id="employeeList" headerText="{i18n>ListOfAllEmployees}" items="{/
Employees}">
<items>
<StandardListItem
title="{FirstName} {LastName}"
iconDensityAware="false"
iconInset="false"
type="Navigation"
press="onListItemPressed"/>
</items>
</List>
</content>
</Page>
</mvc:View>
It’s time to change the EmployeeList view so that we can navigate to the new view. We set the attribute type of
the StandardListItem template to Navigation to make the item clickable and indicate a navigation feature to
the user. Additionally, we add an event handler for the press event that is called when the user clicks on an
employee list item.
webapp/controller/employee/EmployeeList.controller.js
sap.ui.define([
"sap/ui/demo/nav/controller/BaseController"
], function (BaseController) {
"use strict";
return
BaseController.extend("sap.ui.demo.nav.controller.employee.EmployeeList", {
onListItemPressed : function(oEvent){
var oItem, oCtx;
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
301
});
});
}
oItem = oEvent.getSource();
oCtx = oItem.getBindingContext();
this.getRouter().navTo("employee",{
employeeId : oCtx.getProperty("EmployeeID")
});
Finally, we add the handler onListItemPressed for the press event to the EmployeeList controller. In the
handler, we determine the EmployeeID of the list item by querying the binding context and accessing the property
EmployeeID from the data model.
Then we navigate to the employee route and pass a configuration object on to the navTo method with the
mandatory parameter employeeId filled with the correct EmployeeID. The router always makes sure that
mandatory parameters as specified in the route’s pattern are set; otherwise an error is thrown.
webapp/i18n/i18n.properties
...
EmployeeDetailsOf=Employee Details of
EmployeeIDColon=Employee ID:
FirstName=First Name
LastName=Last Name
Address=Address
City=City
PostalCode=Postal Code
PhoneHome=Phone (Home)
Country=Country
Add the new texts to the i18n.properties file.
That’s it. You can go to webapp/index.html#/employees and click on any list item to be redirected to
corresponding employee’s details. Check also what happens when you directly navigate to the following files:
● webapp/index.html#/employees/3
● webapp/index.html#/employees/33
Related Information
API Reference: sap.ui.model.Binding
Step 8: Navigate with Flip Transition
In this step, we want to illustrate how to navigate to a page with a custom transition animation. Both forward and
backward navigation will use the “flip” transition but with a different direction. We will create a simple link on the
Employee view that triggers a flip navigation to a page that displays the resume data of a certain employee.
Pressing the Back button will navigate back to the Employee view with a reversed flip transition.
302
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Preview
Figure 80: Employee Details page with Flip to Resume link
Figure 81: Resume page with multiple tabs
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
303
Figure 82: Not Found page for resume
Coding
You can view and download all files in the Samples in the Demo Kit at Routing and Navigation - Step 8.
304
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Figure 83: Folder structure for this step
webapp/view/employee/Employee.view.xml
<mvc:View
controllerName="sap.ui.demo.nav.controller.employee.Employee"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
xmlns:f="sap.ui.layout.form"
busyIndicatorDelay="0">
<Page
id="employeePage"
title="{i18n>EmployeeDetailsOf} {FirstName} {LastName}"
showNavButton="true"
navButtonPress="onNavBack"
class="sapUiResponsiveContentPadding">
<content>
<Panel
id="employeePanel"
width="auto"
class="sapUiResponsiveMargin sapUiNoContentPadding">
<headerToolbar>
<Toolbar>
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
305
level="H2"/>
<Title text="{i18n>EmployeeIDColon} {EmployeeID}"
<ToolbarSpacer />
<Link text="{i18n>FlipToResume}"
tooltip="{i18n>FlipToResume.tooltip}" press="onShowResume" />
</Toolbar>
</headerToolbar>
<content>
...
</content>
</Panel>
</content>
</Page>
</mvc:View>
First we add the Flip to Resume link to the Employee Details view to trigger the navigation to the resume of the
employee that is currently displayed.
webapp/controller/employee/Employee.controller.js
sap.ui.define([
"sap/ui/demo/nav/controller/BaseController"
], function (BaseController) {
"use strict";
return BaseController.extend("sap.ui.demo.nav.controller.employee.Employee", {
...
_onBindingChange : function (oEvent) {
// No data for the binding
if (!this.getView().getBindingContext()) {
this.getRouter().getTargets().display("notFound");
}
}
...
},
onShowResume : function (oEvent) {
var oCtx = this.getView().getElementBinding().getBoundContext();
});
});
}
this.getRouter().navTo("employeeResume", {
employeeId : oCtx.getProperty("EmployeeID")
});
Then we change the Employee.controller.js file by adding the press handler onShowResume for the Flip to
Resume link. The handler simply navigates to a new route employeeResume and fills the mandatory parameter
employeeId with the property EmployeeID from the view’s bound context. The route employeeResume is not
available yet, so we will have to add it to our routing configuration.
webapp/manifest.json
{
306
"_version": "1.8.0",
"sap.app": {
...
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
}
},
"sap.ui": {
...
},
"sap.ui5": {
...
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "sap.ui.demo.nav.view",
"controlId": "app",
"controlAggregation": "pages",
"transition": "slide",
"bypassed": {
"target": "notFound"
}
},
"routes": [{
"pattern": "",
"name": "appHome",
"target": "home"
}, {
"pattern": "employees",
"name": "employeeList",
"target": "employees"
}, {
"pattern": "employees/{employeeId}",
"name": "employee",
"target": "employee"
}, {
"pattern": "employees/{employeeId}/resume",
"name": "employeeResume",
"target": "employeeResume"
}],
"targets": {
"home": {
"viewId": "home",
"viewName": "Home",
"viewLevel" : 1
},
"notFound": {
"viewId": "notFound",
"viewName": "NotFound",
"transition": "show"
},
"employees": {
"viewId": "employees",
"viewPath": "sap.ui.demo.nav.view.employee",
"viewName": "EmployeeList",
"viewLevel" : 2
},
"employee": {
"viewId": "employee",
"viewName": "employee.Employee",
"viewLevel" : 3
},
"employeeResume": {
"viewId": "resume",
"viewName": "employee.Resume",
"viewLevel" : 4,
"transition": "flip"
}
}
}
}
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
307
In the routing configuration, we add a new route employeeResume which references a target with the same name.
The route’s pattern expects an {employeeId} as a mandatory parameter and ends with the static string /
resume.
The target employeeResume references the view employee.Resume that we are about to create. The target’s
viewLevel is 4; compared to the employee target this is one level lower again. To configure a flip navigation, we
simply set the transition of our target to flip. Together with the correct viewLevel configuration this will trigger
the correct forward and backward flip navigation whenever the target is displayed.
Note
Possible values for the transition parameter are:
● slide (default)
● flip
● show
● fade
You can also implement your own transitions and add it to a control that extends sap.m.NavContainer (for
example, sap.m.App or sap.m.SplitApp). For more information, see the API Reference for NavContainer.
webapp/view/employee/Resume.view.xml (New)
<mvc:View
controllerName="sap.ui.demo.nav.controller.employee.Resume"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
xmlns:f="sap.ui.layout.form"
busyIndicatorDelay="0">
<Page
title="{i18n>ResumeOf} {FirstName} {LastName}"
id="employeeResumePage"
showNavButton="true"
navButtonPress="onNavBack"
class="sapUiResponsiveContentPadding">
<content>
<IconTabBar
id="iconTabBar"
class="sapUiResponsiveContentPadding"
binding="{Resume}">
<items>
<IconTabFilter id="infoTab" text="{i18n>Info}" key="Info">
<Text text="{Information}" />
</IconTabFilter>
<IconTabFilter id="projectsTab" text="{i18n>Projects}"
key="Projects">
<mvc:XMLView
viewName="sap.ui.demo.nav.view.employee.ResumeProjects"></mvc:XMLView>
</IconTabFilter>
<IconTabFilter id="hobbiesTab" text="{i18n>Hobbies}"
key="Hobbies">
<Text text="{Hobbies}" />
</IconTabFilter>
<IconTabFilter id="notesTab" text="{i18n>Notes}" key="Notes">
<Text text="{Notes}" />
</IconTabFilter>
</items>
308
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
</IconTabBar>
</content>
</Page>
</mvc:View>
Create a file Resume.view.xml inside the webapp/view/employee folder. The view uses an IconTabBar to
display the resume data. Therefore, its binding attribute is set to {Resume}.
In the IconTabBar we display four tabs. Three of them simply use a Text control to display the data from the
service. The Projects tab uses a nested XML view to display the projects of the employee. OpenUI5 takes care of
loading the XML view automatically when the user navigates to the Resume page.
webapp/controller/employee/Resume.controller.js (New)
sap.ui.define([
"sap/ui/demo/nav/controller/BaseController"
], function (BaseController) {
"use strict";
return BaseController.extend("sap.ui.demo.nav.controller.employee.Resume", {
onInit: function () {
var oRouter = this.getRouter();
oRouter.getRoute("employeeResume").attachMatched(this._onRouteMatched,
this);
},
_onRouteMatched : function (oEvent) {
var oArgs, oView;
oArgs = oEvent.getParameter("arguments");
oView = this.getView();
oView.bindElement({
path : "/Employees(" + oArgs.employeeId + ")",
events : {
change: this._onBindingChange.bind(this),
dataRequested: function (oEvent) {
oView.setBusy(true);
},
dataReceived: function (oEvent) {
oView.setBusy(false);
}
}
});
},
_onBindingChange : function (oEvent) {
// No data for the binding
if (!this.getView().getBindingContext()) {
this.getRouter().getTargets().display("notFound");
}
}
});
});
Create a file Resumee.controller.js in the webapp/controller/employee folder. In this controller, we
make sure to bind the view to the correct employee whenever the employeeResume route has matched. We have
already used this approach in the previous step so you should be able to recognize the building blocks in the code
above. Again, in case the user cannot be found we display the notFound target.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
309
webapp/view/employee/ResumeProjects.view.xml (New)
<mvc:View xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc">
<Text text="{Projects}" />
</mvc:View>
Create a file ResumeProjects.view.xml in the webapp/view/employee folder. This view does not have a
controller as we don’t need it. It just displays a Text control with the projects text of the selected employee. It
illustrates that using nested views works just fine in combination with navigation and routing in OpenUI5.
Note
For more complex applications, the performance is significantly increased if parts of the UI are only loaded
when the user is actively selecting it. In this example, the view is always loaded even though the user never
decided to display the project information. In the next steps, we will extend the UI so that the content is loaded
“lazy” by OpenUI5 only when the filter item is clicked. The back-end service will fetch the data only on request
and the UI will only have to be updated with the selected data instead of loading all data.
webapp/i18n/i18n.properties
...
ResumeOf=Resume of
Info=Info
Projects=Projects
Hobbies=Hobbies
Notes=Notes
FlipToResume=Flip to Resume
FlipToResume.tooltip=See the resume of this employee
Add the new texts to the i18n.properties file.
You can go to webapp/index.html#/employees/3 and click on the Flip to Resume link to be redirected with a
nice flip transition to the employee’s resume. The back navigation uses a reverse flip navigation to get back to the
Employee Details page. You can also directly navigate to webapp/index.html#/employees/3/resume or
webapp/index.html#/employees/33/resume to see what happens.
Related Information
API Reference: sap.m.NavContainer
API Overview and Samples: sap.m.NavContainer
310
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Step 9: Allow Bookmarkable Tabs with Optional Query
Parameters
The resume view contains four tabs as we have seen in the previous steps. However, when the user navigates to
the resume page, only the first tab is displayed initially. Navigating directly to a specific tab or bookmarking a tab is
not yet supported in our current app.
In this step, we implement a bookmarking feature by enabling deep linking to tabs with optional query parameters.
A deep link is basically a link that directly references a deeper structure and parameters of the app in the URL. It is
often bookmarked or shared to have a convenient entry point into the app for a certain task or action. The selected
tab should be reflected in the URL but the tab can also be omitted, for example, when we initially navigate to the
resume page.
Preview
Figure 84: Deep link to allow bookmarkable tabs
Coding
You can view and download all files in the Samples in the Demo Kit at Routing and Navigation - Step 9.
webapp/manifest.json
{
"_version": "1.8.0",
"sap.app": {
...
},
"sap.ui": {
...
},
"sap.ui5": {
...
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
311
}
}
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "sap.ui.demo.nav.view",
"controlId": "app",
"controlAggregation": "pages",
"transition": "slide",
"bypassed": {
"target": "notFound"
}
},
"routes": [{
"pattern": "",
"name": "appHome",
"target": "home"
}, {
"pattern": "employees",
"name": "employeeList",
"target": "employees"
}, {
"pattern": "employees/{employeeId}",
"name": "employee",
"target": "employee"
}, {
"pattern": "employees/{employeeId}/resume:?query:",
"name": "employeeResume",
"target": "employeeResume"
}],
"targets": {
...
}
}
Up until now, you could only navigate to an employee’s resume with the deep link webapp/index.html#/
employees/3/resume. This will always select the first tab as implemented by the IconTabBar control. In order
to open the page directly with a specific tab selected and to make the tabs bookmarkable, we add the query
parameter to the URL pattern.
This allows URLs like webapp/index.html#/employees/3/resume?tab=Projects where the query
parameter defines which tab shall be displayed. We change the pattern of the employeeResume route to
employees/{employeeId}/resume:?query:. The new part :?query: allows to pass on queries with any
parameters, for example, the hash /#/employees/3/resume?tab=Projects or /#/employees/3/resume?
tab=Projects&action=edit matches the pattern and can be processed in the matched event.
The :?query: parameter starts and ends with :; this means that it is optional. If you want to make it mandatory,
you can use the {?query} syntax (everything in between {} is considered as being mandatory).
webapp/view/employee/Resume.view.xml
<mvc:View
controllerName="sap.ui.demo.nav.controller.employee.Resume"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
xmlns:f="sap.ui.layout.form"
busyIndicatorDelay="0">
<Page
312
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
title="{i18n>ResumeOf} {FirstName} {LastName}"
id="employeeResumePage"
showNavButton="true"
navButtonPress="onNavBack"
class="sapUiResponsiveContentPadding">
<content>
<IconTabBar
id="iconTabBar"
class="sapUiResponsiveContentPadding"
binding="{Resume}"
select="onTabSelect"
selectedKey="{view>/selectedTabKey}">
<items>
<IconTabFilter id="infoTab" text="{i18n>Info}" key="Info">
<Text text="{Information}" />
</IconTabFilter>
<IconTabFilter id="projectsTab" text="{i18n>Projects}"
key="Projects">
<mvc:XMLView
viewName="sap.ui.demo.nav.view.employee.ResumeProjects"></mvc:XMLView>
</IconTabFilter>
<IconTabFilter id="hobbiesTab" text="{i18n>Hobbies}"
key="Hobbies">
<Text text="{Hobbies}" />
</IconTabFilter>
<IconTabFilter id="notesTab" text="{i18n>Notes}" key="Notes">
<Text text="{Notes}" />
</IconTabFilter>
</items>
</IconTabBar>
</content>
</Page>
</mvc:View>
To update the currently selected tab in the URL we listen to the select event of the IconTabBar by setting
select="onTabSelect" in the resume view. The selectedKey is bound to a view model. This allows to easily
change the selectedKey according to the selected tab in the URL.
webapp/controller/employee/Resume.controller.js
sap.ui.define([
"sap/ui/demo/nav/controller/BaseController",
"sap/ui/model/json/JSONModel"
], function (BaseController, JSONModel) {
"use strict";
var _aValidTabKeys = ["Info", "Projects", "Hobbies", "Notes"];
return BaseController.extend("sap.ui.demo.nav.controller.employee.Resume", {
onInit: function () {
var oRouter = this.getRouter();
this.getView().setModel(new JSONModel(), "view");
oRouter.getRoute("employeeResume").attachMatched(this._onRouteMatched,
this);
},
_onRouteMatched : function (oEvent) {
var oArgs, oView, oQuery;
oArgs = oEvent.getParameter("arguments");
oView = this.getView();
oView.bindElement({
path : "/Employees(" + oArgs.employeeId + ")",
events : {
change: this._onBindingChange.bind(this),
dataRequested: function (oEvent) {
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
313
oView.setBusy(true);
},
dataReceived: function (oEvent) {
oView.setBusy(false);
}
}
});
oQuery = oArgs["?query"];
if (oQuery && _aValidTabKeys.indexOf(oQuery.tab) > -1){
oView.getModel("view").setProperty("/selectedTabKey", oQuery.tab);
} else {
// the default query param should be visible at all time
this.getRouter().navTo("employeeResume", {
employeeId : oArgs.employeeId,
query: {
tab : _aValidTabKeys[0]
}
},true /*no history*/);
}
});
});
},
_onBindingChange : function (oEvent) {
// No data for the binding
if (!this.getView().getBindingContext()) {
this.getRouter().getTargets().display("notFound");
}
},
onTabSelect : function (oEvent){
var oCtx = this.getView().getBindingContext();
this.getRouter().navTo("employeeResume", {
employeeId : oCtx.getProperty("EmployeeID"),
query: {
tab : oEvent.getParameter("selectedKey")
}
}, true /*without history*/);
}
When a tab is selected manually, its select handler is called. Therefore, let’s first have a look at the onTabSelect
event handler that is added at the end of the resume controller. It detects the selectedKey of the tab and
navigates to the employeeResume route to update the URL in the address bar. Additionally to the mandatory
parameter employeeId, we pass on a custom query object with a parameter tab and fill it with the
selectedKey value that we receive from the select event of the IconTabBar. By passing on true as the third
argument we replace the current history to make sure that manually clicked tabs won’t be added to the browser
history.
A dependency to sap/ui/model/json/JSONModel is added to the controller. Now, we modify the onInit
function to instantiate a JSONModel and use it as the view model. _aValidTabKeys is added to the controller.
We want to make sure that only valid tabs can be selected. Therefore, the array _aValidTabKeys contains all
allowed tab keys that we can check against to validate the tab parameter from the URL later. The keys are equal to
the keys of our IconTabFilters in the resume view.
In the _onRouteMatched event handler, we add the oQuery variable to store a reference to the query object from
the router. This allows a more comfortable access to the query object.
In case a query object is passed on and the tab parameter has a valid value, we display the specific tab by
updating the property /selectedTabKey in the view model. As the selectedKey property of the IconTabBar is
bound to {view>/selectedTabKey} the corresponding tab is selected.
314
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
The else case is called when either no or an invalid tab parameter is specified. We navigate to the Info tab to make
sure that the tab parameter is reflected in the URL at all times. The actual requirements of your app might differ,
feel free to change it accordingly...
From now on our tabs are bookmarkable. Try to access the following (deep) links directly:
● webapp/index.html#/employees/3/resume
● webapp/index.html#/employees/3/resume?tab=Info
● webapp/index.html#/employees/3/resume?tab=Projects
● webapp/index.html#/employees/3/resume?tab=Hobbies
● webapp/index.html#/employees/3/resume?tab=Notes
● webapp/index.html#/employees/3/resume?tab=SomethingInvalid
When you click on any tab you will see that the hash in the URL changes immediately, and when you change the
hash in the URL parameter manually, you can see that the UI is also updated accordingly.
Related Information
API Reference: sap.m.IconTabBar
Step 10: Implement “Lazy Loading”
In the previous steps, we have implemented a Resume view that uses tabs to display data. The complete content of
the tabs is loaded once, no matter which tab is currently displayed. We can increase the performance of our app by
avoiding to load content that is not visible. Therefore, we implement a “lazy loading” feature that only loads the
view and data when requested by the user.
Preview
Figure 85: Tabs with lazy loading
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
315
Coding
You can view and download all files in the Samples in the Demo Kit at Routing and Navigation - Step 10 .
Figure 86: Folder Structure for this Step
webapp/view/employee/Resume.view.xml
<mvc:View
controllerName="sap.ui.demo.nav.controller.employee.Resume"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
xmlns:f="sap.ui.layout.form"
busyIndicatorDelay="0">
<Page
title="{i18n>ResumeOf} {FirstName} {LastName}"
id="employeeResumePage"
showNavButton="true"
navButtonPress="onNavBack"
316
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
class="sapUiResponsiveContentPadding">
<content>
<IconTabBar
id="iconTabBar"
class="sapUiResponsiveContentPadding"
binding="{Resume}"
select="onTabSelect"
selectedKey="{view>/selectedTabKey}">
<items>
<IconTabFilter id="infoTab" text="{i18n>Info}" key="Info">
<Text text="{Information}" />
</IconTabFilter>
<IconTabFilter id="projectsTab" text="{i18n>Projects}"
key="Projects">
<mvc:XMLView
viewName="sap.ui.demo.nav.view.employee.ResumeProjects"></mvc:XMLView>
</IconTabFilter>
<IconTabFilter id="hobbiesTab" text="{i18n>Hobbies}"
key="Hobbies">
<!-- place content via lazy loading -->
</IconTabFilter>
<IconTabFilter id="notesTab" text="{i18n>Notes}" key="Notes">
<!-- place content via lazy loading -->
</IconTabFilter>
</items>
</IconTabBar>
</content>
</Page>
</mvc:View>
To illustrate lazy loading, we implement that the content is loaded only when the user selects the corresponding
tab for two of our tabs from the IconTabBar: Hobbies and Notes. The IconTabFilter controls each have a hardcoded ID so that we can address them later in our routing configuration. In real use cases, you would do this for
tabs that contain a lot of content or trigger expensive service calls to a back-end service.
In the resume view we remove the content of the Hobbies and Notes tabs as we will now fill it dynamically with
navigation features.
webapp/view/employee/ResumeHobbies.view.xml (New)
<mvc:View xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc">
<Text text="{Hobbies}" />
</mvc:View>
Create the file ResumeHobbies.view.xml in the webapp/view/employee folder. Move the content for the tab
that was previously in the resume view to that view. We don’t need a controller for this view as there is no
additional logic involved. This view will be lazy-loaded and placed into the content of the Hobbies tab with
navigation features.
webapp/view/employee/ResumeNotes.view.xml (New)
<mvc:View xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc">
<Text text="{Notes}" />
</mvc:View>
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
317
Create the file ResumeNotes.view.xml in the webapp/view/employee folder similar to the Hobbies view to
transform this tab to a separate view as well.
webapp/controller/employee/Resume.controller.js
sap.ui.define([
"sap/ui/demo/nav/controller/BaseController",
"sap/ui/model/json/JSONModel"
], function (BaseController, JSONModel) {
"use strict";
var _aValidTabKeys = ["Info", "Projects", "Hobbies", "Notes"];
return BaseController.extend("sap.ui.demo.nav.controller.employee.Resume", {
...
_onRouteMatched : function (oEvent) {
var oArgs, oView, oQuery;
oArgs = oEvent.getParameter("arguments");
oView = this.getView();
oView.bindElement({
...
});
oQuery = oArgs["?query"];
if (oQuery && _aValidTabKeys.indexOf(oQuery.tab) > -1){
oView.getModel("view").setProperty("/selectedTabKey", oQuery.tab);
// support lazy loading for the hobbies and notes tab
if (oQuery.tab === "Hobbies" || oQuery.tab === "Notes"){
// the target is either "resumeTabHobbies" or "resumeTabNotes"
this.getRouter().getTargets().display("resumeTab" + oQuery.tab);
}
} else {
// the default query param should be visible at all time
this.getRouter().navTo("employeeResume", {
employeeId : oArgs.employeeId,
query: {
tab : _aValidTabKeys[0]
}
},true /*no history*/);
}
},
...
});
});
Now we extend the resume controller a little and add additional logic to the part of the _onRouteMatched
function where a new tab has been selected and validated. In case the selectedKey matches Hobbies or Notes
we call this.getRouter().getTargets().display("resumeTab" + oQuery.tab) to display the
corresponding target manually. Here the valid targets are resumeTabHobbies and resumeTabNotes as we have
changed the behavior for these two tabs by creating separate views.
These lines of code make sure that the targets are only loaded when they are needed (“lazy loading”). But the
router does not know the new targets yet, so let’s create them in our routing configuration.
webapp/manifest.json
{
318
"_version": "1.8.0",
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
}
"sap.app": {
...
},
"sap.ui": {
...
},
"sap.ui5": {
...
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "sap.ui.demo.nav.view",
"controlId": "app",
"controlAggregation": "pages",
"transition": "slide",
"bypassed": {
"target": "notFound"
}
},
"routes": [{
...
}, {
"pattern": "employees/{employeeId}/resume:?query:",
"name": "employeeResume",
"target": "employeeResume"
}],
"targets": {
...
"employeeResume": {
"viewId": "resume",
"viewName": "employee.Resume",
"viewLevel" : 4,
"transition": "flip"
},
"resumeTabHobbies": {
"viewId": "resumeHobbies",
"parent": "employeeResume",
"viewPath": "sap.ui.demo.nav.view.employee",
"viewName": "ResumeHobbies",
"controlId": "hobbiesTab",
"controlAggregation": "content"
},
"resumeTabNotes": {
"viewId": "resumeNotes",
"parent": "employeeResume",
"viewPath": "sap.ui.demo.nav.view.employee",
"viewName": "ResumeNotes",
"controlId": "notesTab",
"controlAggregation": "content"
}
}
}
}
We add the resumeTabHobbies and resumeTabNotes targets to the descriptor file with additional fields that
override the default configuration as we now want to display the targets locally inside the IconTabBar control and
not as pages of the app.
The resumeTabHobbies target sets the parent property to employeeResume. The parent property expects the
name of another target. In our case, this makes sure that the view from the parent target employeeResume is
loaded before the target resumeTabHobbies is displayed. This can be considered as a “view dependency”. By
setting the controlId and controlAggregation properties the router places the view ResumeHobbies into
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
319
the content aggregation of the IconTabFilter control with ID hobbiesTab. We also set a parameter viewId to
a custom ID to illustrate how you could overrule a hard-coded ID inside a view.
Note
Each target can define only one parent with its parent property. This is similar to the OpenUI5 control tree where
each control can have only one parent control (accessed with the method getParent() of
sap.ui.base.ManagedObject). The controlId property always references a control inside the parent view
that is specified with the parent target.
Now we add the resumeTabNotes target similar to the Hobbies target. The resumeTabNotes target defines the
parent target employeeResume as well, because they share the same parent view. We place the ResumeNotes
view into the content aggregation of the IconTabFilter control with ID notesTab.
We have now implemented lazy loading for the tabs Hobbies and Notes. These two tabs are now managed by the
routing configuration and only loaded when we click on them the first time.
Try it out yourself: Open the Network tab of your browser's developer tools and click on the tabs of your app. In the
network traffic you will see that ResumeHobbies.view.xml file is only loaded when the Hobbies tab is displayed
the first time. The same applies for the Notes tab. Mission accomplished!
Conventions
● Lazy-load content that is not initially displayed to the user
Related Information
API Reference: ap.m.routing.Targets
Step 11: Assign Multiple Targets
In this step, we will add a new button to the home page to illustrate the usage of multiple targets for a route. When
the button is pressed, a new page opens that contains two parts: a header part at the top and a content part. The
content part displays a table of employees that can be sorted and searched. We will use the array notation in the
routing configuration to assign multiple targets to a route - a feature that we have not yet introduced.
320
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Preview
Figure 87: New button Show Employee Overview
Figure 88: Employee Overview with search field
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
321
Figure 89: Sort options for the Employee Overview
Coding
You can view and download all files in the Demo Kit at Routing and Navigation - Step 11 .
Figure 90: Folder Structure for this Step
webapp/view/Home.view.xml
<mvc:View
controllerName="sap.ui.demo.nav.controller.Home"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Page title="{i18n>homePageTitle}" class="sapUiResponsiveContentPadding">
<content>
<Button id="displayNotFoundBtn" text="{i18n>DisplayNotFound}"
press="onDisplayNotFound" class="sapUiTinyMarginEnd"/>
<Button id="employeeListBtn" text="{i18n>ShowEmployeeList}"
press="onNavToEmployees" class="sapUiTinyMarginEnd"/>
<Button id="employeeOverviewBtn" text="{i18n>ShowEmployeeOverview}"
press="onNavToEmployeeOverview" class="sapUiTinyMarginEnd"/>
</content>
</Page>
</mvc:View>
First we add a new button to the Home view and add an event handler for the press event.
322
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/controller/Home.controller.js
sap.ui.define([
"sap/ui/demo/nav/controller/BaseController"
], function (BaseController) {
"use strict";
return BaseController.extend("sap.ui.demo.nav.controller.Home", {
...
onNavToEmployees : function (oEvent){
this.getRouter().navTo("employeeList");
},
onNavToEmployeeOverview : function (oEvent) {
this.getRouter().navTo("employeeOverview");
}
});
});
As you know already from the previous steps, we add the press event handler onNavToEmployeeOverview. It
navigates to the route employeeOverview which does not exist yet, so let’s create it.
webapp/manifest.json
{
"_version": "1.8.0",
"sap.app": {
...
},
"sap.ui": {
...
},
"sap.ui5": {
...
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "sap.ui.demo.nav.view",
"controlId": "app",
"controlAggregation": "pages",
"transition": "slide",
"bypassed": {
"target": "notFound"
}
},
"routes": [{
"pattern": "",
"name": "appHome",
"target": "home"
}, {
"pattern": "employees",
"name": "employeeList",
"target": "employees"
}, {
"pattern": "employees/overview",
"name": "employeeOverview",
"target": ["employeeOverviewTop", "employeeOverviewContent"]
}, {
"pattern": "employees/{employeeId}",
"name": "employee",
"target": "employee"
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
323
}, {
"pattern": "employees/{employeeId}/resume:?query:",
"name": "employeeResume",
"target": "employeeResume"
}
}
}
}],
"targets": {
...
"resumeTabNotes": {
"viewId": "resumeNotes",
"parent": "employeeResume",
"viewPath": "sap.ui.demo.nav.view.employee",
"viewName": "ResumeNotes",
"controlId": "notesTab",
"controlAggregation": "content"
},
"employeeOverview": {
"viewId": "employeeOverview",
"viewPath": "sap.ui.demo.nav.view.employee.overview",
"viewName": "EmployeeOverview",
"viewLevel" : 2
},
"employeeOverviewTop": {
"viewId": "employeeOverviewTop",
"parent": "employeeOverview",
"viewPath": "sap.ui.demo.nav.view.employee.overview",
"viewName": "EmployeeOverviewTop",
"controlId": "EmployeeOverviewParent",
"controlAggregation": "content"
},
"employeeOverviewContent": {
"viewId": "employeeOverviewContent",
"parent": "employeeOverview",
"viewPath": "sap.ui.demo.nav.view.employee.overview",
"viewName": "EmployeeOverviewContent",
"controlId": "EmployeeOverviewParent",
"controlAggregation": "content"
}
}
We extend our current routing configuration with a new route employeeOverview. Note that this route has to be
configured before the employee route, else the employee route would be matched with a hash like /#/
employees/overview. The new route employeeOverview references two targets at the same time with an
array notation: employeeOverviewTop and employeeOverviewContent. As you can see here, a route can
reference an arbitrary number of targets that will be displayed when the route is matched.
Both targets employeeOverviewTop and employeeOverviewContent reference the target
employeeOverview as their parent target because we want to place them both inside the parent. Please also
note that we also introduce a new layer overview in the viewPath property.
Note
The order of the routing configuration matters here, because the router stops matching additional routes when
the first match is found. You can override this behavior if you set parameter greedy to true on the route. Then
the route will always be matched when the pattern matches the current URL, even if another route has been
matched before. The greedy option comes from the underlying Crossroads.js library, a popular routing
library. A common use case for using greedy is configuring targets without views and then listening for routematched events.
324
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Now we create both targets employeeOverviewTop and employeeOverviewContent as well as their parent
target employeeOverview. On the parent target we set viewLevel to 2 to ensure a correct transition animation.
In the targets, we also configure where the corresponding views of the children shall be displayed by setting the
parameters controlId and controlAggregation to a control ID of a sap.ui.layout.HorizontalLayout
that we are about to create in a new view. You should be familiar with this configuration from the last step.
The router makes sure that the parent view is loaded in addition to the target view when a corresponding route has
been matched and the targets are displayed. The referenced views are displayed automatically at the configured
place in the parent’s view, in our case in the content aggregation of the page control. We have mentioned three
different views that we still need to add to the app to make the configuration work:
● EmployeeOverview
● EmployeeOverviewTop
● EmployeeOverviewContent
webapp/view/employee/overview/EmployeeOverview.view.xml (New)
<mvc:View
controllerName="sap.ui.demo.nav.controller.employee.overview.EmployeeOverview"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
xmlns:l="sap.ui.layout">
<Page id="EmployeeOverviewParent" title="{i18n>EmployeeOverview}"
showNavButton="true"
navButtonPress="onNavBack"
class="sapUiResponsiveContentPadding">
<content>
<!-- inserted by routing -->
</content>
</Page>
</mvc:View>
First we create the parent view by creating the folder overview under webapp/view/employee and placing the
file EmployeeOverview.view.xml into that folder. This view contains a Page control that is referenced from the
targets in our manifest.json descriptor file. The content aggregation of the page will be filled by the router with
the top and content part when the corresponding route has been hit.
webapp/controller/employee/overview/EmployeeOverview.controller.js (New)
sap.ui.define([
"sap/ui/demo/nav/controller/BaseController"
], function (BaseController) {
"use strict";
return
BaseController.extend("sap.ui.demo.nav.controller.employee.overview.EmployeeOverview
", {
});
});
The controller does not contain any logic yet, but we will add back navigation features here in the next steps.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
325
webapp/view/employee/overview/EmployeeOverviewTop.view.xml (New)
<mvc:View xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc" class="sapUiMediumMarginBottom">
<Title text="{i18n>EmployeeOverviewTop}" />
</mvc:View>
Create the file EmployeeOverviewTop.view.xml and place it in the webapp/view/employee/overview
folder. This view displays a static text for illustration purposes. Change it according to your own requirements. We
don’t need a controller for this view
webapp/view/employee/overview/EmployeeOverviewContent.view.xml (New)
<mvc:View
controllerName="sap.ui.demo.nav.controller.employee.overview.EmployeeOverviewContent
"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Table id="employeesTable"
items="{/Employees}">
<headerToolbar>
<Toolbar>
<Title text="{i18n>Employees}" level="H2"/>
<ToolbarSpacer />
<SearchField id="searchField" search="onSearchEmployeesTable"
width="50%"/>
<Button icon="sap-icon://sort" press="onSortButtonPressed" />
</Toolbar>
</headerToolbar>
<columns>
<Column id="employeeIDCol"><Text text="{i18n>EmployeeID}"/></Column>
<Column id="firstNameCol" demandPopin="true"><Text
text="{i18n>FirstName}"/></Column>
<Column id="lastNameCol" demandPopin="true"><Text
text="{i18n>LastName}"/></Column>
<Column id="addressCol" minScreenWidth="Tablet"
demandPopin="true"><Text text="{i18n>Address}"/></Column>
<Column id="cityCol" minScreenWidth="Tablet" demandPopin="true"><Text
text="{i18n>City}"/></Column>
<Column id="regionCol" minScreenWidth="Tablet" demandPopin="true"><Text
text="{i18n>Region}"/></Column>
<Column id="postalCodeCol" minScreenWidth="Tablet"
demandPopin="true"><Text text="{i18n>PostalCode}"/></Column>
<Column id="countryCol" minScreenWidth="Tablet"
demandPopin="true"><Text text="{i18n>Country}"/></Column>
<Column id="homePhoneCol" minScreenWidth="Tablet" demandPopin="true"
hAlign="End"><Text text="{i18n>Phone}"/></Column>
</columns>
<items>
<ColumnListItem>
<cells>
<Text text="{EmployeeID}"/>
<Text text="{FirstName}"/>
<Text text="{LastName}"/>
<Text text="{Address}"/>
<Text text="{City}"/>
<Text text="{Region}"/>
<Text text="{PostalCode}"/>
<Text text="{Country}"/>
<Text text="{HomePhone}"/>
326
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
</cells>
</ColumnListItem>
</items>
</Table>
</mvc:View>
Create the file EmployeeOverviewContent.view.xml in the webapp/view/employee/overview folder. This view
displays a responsive table with several columns containing employee data like Employee ID, First Name, Last
Name and so on. In the headerToolbar, we add the SearchField and a Button. The SearchField in the
header area allows to search in the table. The Button next to it opens a dialog to adjust the sorting of the table.
webapp/controller/employee/overview/
EmployeeOverviewContent.controller.js (New)
sap.ui.define([
"sap/ui/demo/nav/controller/BaseController",
"sap/ui/model/Filter",
"sap/ui/model/FilterOperator",
"sap/ui/model/Sorter"
], function (BaseController, Filter, FilterOperator, Sorter) {
"use strict";
return
BaseController.extend("sap.ui.demo.nav.controller.employee.overview.EmployeeOverview
Content", {
onInit: function () {
this._oTable = this.byId("employeesTable");
this._oVSD = null;
this._sSortField = null;
this._bSortDescending = false;
this._aValidSortFields = ["EmployeeID", "FirstName", "LastName"];
this._sSearchQuery = null;
this._initViewSettingsDialog();
},
onSortButtonPressed : function (oEvent) {
this._oVSD.open();
},
onSearchEmployeesTable : function (oEvent) {
var sQuery = oEvent.getSource().getValue();
this._applySearchFilter( oEvent.getSource().getValue() );
},
_initViewSettingsDialog : function () {
var oRouter = this.getRouter();
this._oVSD = new sap.m.ViewSettingsDialog("vsd", {
confirm: function (oEvent) {
var oSortItem = oEvent.getParameter("sortItem");
this._applySorter(oSortItem.getKey(),
oEvent.getParameter("sortDescending"));
}.bind(this)
});
// init sorting (with simple sorters as custom data for all fields)
this._oVSD.addSortItem(new sap.m.ViewSettingsItem({
key: "EmployeeID",
text: "Employee ID",
selected: true
// we do this because our MockData is sorted
anyway by EmployeeID
}));
this._oVSD.addSortItem(new sap.m.ViewSettingsItem({
key: "FirstName",
text: "First Name",
selected: false
}));
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
327
this._oVSD.addSortItem(new sap.m.ViewSettingsItem({
key: "LastName",
text: "Last Name",
selected: false
}));
},
_applySearchFilter : function (sSearchQuery) {
var aFilters, oFilter, oBinding;
// first check if we already have this search value
if (this._sSearchQuery === sSearchQuery) {
return;
}
this._sSearchQuery = sSearchQuery;
this.byId("searchField").setValue(sSearchQuery);
// add filters for search
aFilters = [];
if (sSearchQuery && sSearchQuery.length > 0) {
aFilters.push(new Filter("FirstName", FilterOperator.Contains,
sSearchQuery));
aFilters.push(new Filter("LastName", FilterOperator.Contains,
sSearchQuery));
oFilter = new Filter({ filters: aFilters, and: false }); // OR
filter
} else {
oFilter = null;
}
// update list binding
oBinding = this._oTable.getBinding("items");
oBinding.filter(oFilter, "Application");
},
/**
* Applies sorting on our table control.
* @param {string} sSortField
the name of the field used for sorting
* @param {string} sortDescending true or false as a string or boolean
value to specify a descending sorting
* @private
*/
_applySorter : function (sSortField, sortDescending){
var bSortDescending, oBinding, oSorter;
// only continue if we have a valid sort field
if (sSortField && this._aValidSortFields.indexOf(sSortField) > -1) {
// convert the sort order to a boolean value
if (typeof sortDescending === "string") {
bSortDescending = sortDescending === "true";
} else if (typeof sortDescending === "boolean") {
bSortDescending = sortDescending;
} else {
bSortDescending = false;
}
// sort only if the sorter has changed
if (this._sSortField && this._sSortField === sSortField &&
this._bSortDescending === bSortDescending) {
return;
}
this._sSortField = sSortField;
this._bSortDescending = bSortDescending;
oSorter = new Sorter(sSortField, bSortDescending);
// sync with View Settings Dialog
this._syncViewSettingsDialogSorter(sSortField, bSortDescending);
oBinding = this._oTable.getBinding("items");
oBinding.sort(oSorter);
}
},
_syncViewSettingsDialogSorter : function (sSortField, bSortDescending) {
// the possible keys are: "EmployeeID" | "FirstName" | "LastName"
// Note: no input validation is implemented here
this._oVSD.setSelectedSortItem(sSortField);
this._oVSD.setSortDescending(bSortDescending);
328
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
});
});
}
Finally create the controller for the Employee Overview page in the webapp/controller/employee/overview
folder. It basically sets up a ViewSettingsDialog to sort and filter the table of employees and implements event
handlers for the search field and for the sorting of the table.
There is nothing special about this implementation. If you are interested in how to set up a table with sorting and
filtering you can check the corresponding steps of the Walkthrough tutorial or the examples in the Demo Kit. We
will mainly make use of the UI and the functionality for showing additional navigation and routing features.
Therefore, we suggest copying the code and trying it out.
Open webapp/index.html#/employees/overview and check the new views. As you can see, the three views
are wired together automatically by the router based on our configuration in the descriptor. In the top area of the
page, you see a static text and below you see the table filled with data from our test service. The whole routing
functionality that we see in this example is implemented by referencing two targets from one route.
Of course, you can also search the table and change the sorting. When the sorting dialog opens, it creates a block
layer so that the back button and other controls cannot be accessed. However, you can still use the back button of
the browser. As you can see, the dialog is closed automatically by the router before navigating.
Note
The default behavior of the sap.m router is that all dialogs are closed when the hash changes (i.e. when calling
navTo, display or pressing the back button of the browser). You can change this default behavior by calling
getTargetHandler().setCloseDialogs(false) on the router or on the Targets object.
However, we have one problem yet to solve: the search and table ordering are not bookmarkable. Fortunately, we
have additional navigation features at hand and you will see how this works in the next steps
webapp/i18n/i18n.properties
...
EmployeeOverview=Employee Overview
ShowEmployeeOverview=Show Employee Overview
EmployeeOverviewTop=Employee Overview Top
Region=Region
EmployeeID=Employee ID
Phone=Phone
Employees=Employees
Add the new texts to the properties file.
Related Information
API Reference: sap.m.routing.TargetHandler
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
329
API Overview and Samples: sap.ui.core.sample.PatternMatching
Step 12: Make a Search Bookmarkable
In this step we will make the search bookmarkable. This allows users to search for employees in the Employees
table and they can bookmark their search query or share the URL.
Preview
Figure 91: Search and sorting bookmarkable
Coding
You can view and download all files in the Samples in the Demo Kit at Routing and Navigation - Step 12 .
330
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/manifest.json
{
}
"_version": "1.8.0",
"sap.app": {
...
},
"sap.ui": {
...
},
"sap.ui5": {
...
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "sap.ui.demo.nav.view",
"controlId": "app",
"controlAggregation": "pages",
"transition": "slide",
"bypassed": {
"target": "notFound"
}
},
"routes": [{
"pattern": "",
"name": "appHome",
"target": "home"
}, {
"pattern": "employees",
"name": "employeeList",
"target": "employees"
}, {
"pattern": "employees/overview:?query:",
"name": "employeeOverview",
"target": ["employeeOverviewTop", "employeeOverviewContent"]
}, {
"pattern": "employees/{employeeId}",
"name": "employee",
"target": "employee"
}, {
"pattern": "employees/{employeeId}/resume:?query:",
"name": "employeeResume",
"target": "employeeResume"
}],
"targets": {
...
}
}
}
In order to make the search bookmarkable we have to think about how the pattern of the corresponding route
should match the bookmark. We decide to allow /#/employees/overview?search=mySearchQueryString in
order to bookmark a search. Therefore, we simply extend our routing configuration a little. We add the optional :?
query: parameter to the route employeeOverview. We keep in mind that we want to use search as the URL
parameter for the search term that was entered in the search field.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
331
webapp/controller/employee/overview/
EmployeeOverviewContent.controller.js
sap.ui.define([
"sap/ui/demo/nav/controller/BaseController",
"sap/ui/model/Filter",
"sap/ui/model/FilterOperator",
"sap/ui/model/Sorter"
], function (BaseController, Filter, FilterOperator, Sorter) {
"use strict";
return
BaseController.extend("sap.ui.demo.nav.controller.employee.overview.EmployeeOverview
Content", {
onInit: function () {
var oRouter = this.getRouter();
this._oTable = this.byId("employeesTable");
this._oVSD = null;
this._sSortField = null;
this._bSortDescending = false;
this._aValidSortFields = ["EmployeeID", "FirstName", "LastName"];
this._sSearchQuery = null;
this._oRouterArgs = null;
this._initViewSettingsDialog();
// make the search bookmarkable
oRouter.getRoute("employeeOverview").attachMatched(this._onRouteMatched, this);
},
_onRouteMatched : function (oEvent) {
// save the current query state
this._oRouterArgs = oEvent.getParameter("arguments");
this._oRouterArgs.query = this._oRouterArgs["?query"] || {};
delete this._oRouterArgs["?query"];
if (this._oRouterArgs.query) {
// search/filter via URL hash
this._applySearchFilter(this._oRouterArgs.query.search);
}
},
onSortButtonPressed : function (oEvent) {
this._oVSD.open();
},
onSearchEmployeesTable : function (oEvent) {
var oRouter = this.getRouter();
// update the hash with the current search term
this._oRouterArgs.query.search = oEvent.getSource().getValue();
oRouter.navTo("employeeOverview",this._oRouterArgs, true /*no
history*/);
},
...
});
});
Now we handle the optional query parameter from the employeeOverview route in our
EmployeeOverviewContent controller. First we change the onInit function by adding an event listener for the
matched event of the employeeOverview route. Then we buffer the current router arguments as received from
the event. If a query is available, the result from oEvent.getParameter("arguments") will contain a ?query
property with an object of all URL parameters specified, otherwise it is undefined. For an easier access and to
always initialize the query, we save the ?query object containing all query parameters to
this._oRouterArgs.query and delete the duplicate at this._oRouterArgs["?query"]. If we have a search
term query at the search key we continue and call
this._applySearchFilter(this._oRouterArgs.query.search) to trigger a search based on the search
query parameter from the URL.
332
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Storing the arguments objects internally in the controller is important, because we will use the current arguments
when calling navTo() in the search event handler onSearchEmployeesTable and pass on the arguments with
the updated search term. We keep the URL and the UI in sync by navigating to the current target again with the
current value of the search field from the event’s source. The search value is stored in
this._oRouterArgs.query.search together with the other query parameters and it is passed directly to the
router again
That’s it, now our search is bookmarkable and reflected in the URL. Try to access the following pages in your
browser:
● webapp/index.html#/employees/overview
● webapp/index.html#/employees/overview?search=
● webapp/index.html#/employees/overview?search=an
When you change the value in the search field, you see that the hash updates accordingly.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
333
Step 13: Make Table Sorting Bookmarkable
In this step, we will create a button at the top of the table which will change the sorting of the table. When the
current sorting state of the table is changed, the sorting state will be reflected in the URL. This illustrates how to
make the table sorting bookmarkable.
Preview
Figure 92: Bookmarkable search and sorting
Coding
You can view and download all files in the Samples in the Demo Kit at Routing and Navigation - Step 13.
webapp/controller/employee/overview/
EmployeeOverviewContent.controller.js
sap.ui.define([
"sap/ui/demo/nav/controller/BaseController",
"sap/ui/model/Filter",
"sap/ui/model/FilterOperator",
"sap/ui/model/Sorter"
334
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
], function (BaseController, Filter, FilterOperator, Sorter) {
"use strict";
return
BaseController.extend("sap.ui.demo.nav.controller.employee.overview.EmployeeOverview
Content", {
onInit: function () {
...
},
_onRouteMatched : function (oEvent) {
// save the current query state
this._oRouterArgs = oEvent.getParameter("arguments");
this._oRouterArgs.query = this._oRouterArgs["?query"] || {};
delete this._oRouterArgs["?query"];
if (this._oRouterArgs.query) {
// search/filter via URL hash
this._applySearchFilter(this._oRouterArgs.query.search);
// sorting via URL hash
this._applySorter(this._oRouterArgs.query.sortField,
this._oRouterArgs.query.sortDescending);
}
},
...
_initViewSettingsDialog : function () {
var oRouter = this.getRouter();
this._oVSD = new sap.m.ViewSettingsDialog("vsd", {
confirm: function (oEvent) {
var oSortItem = oEvent.getParameter("sortItem");
this._oRouterArgs.query.sortField = oSortItem.getKey();
this._oRouterArgs.query.sortDescending =
oEvent.getParameter("sortDescending");
oRouter.navTo("employeeOverview",this._oRouterArgs, true /
*without history*/);
}.bind(this)
});
...
},
...
});
});
We enhance the EmployeeOverviewContent controller further to add support for bookmarking the table’s
sorting options. We expect two query parameters sortField and sortDescending from the URL for configuring
the sorting of the table. In the matched handler of the route employeeOverview we add an additional call to
this._applySorter(this._oRouterArgs.query.sortField,
this._oRouterArgs.query.sortDescending). This triggers the sorting action based on the two query
parameters sortField and sortDescending from the URL.
Next we change the confirm event handlers of our ViewSettingsDialog. The confirm handler updates the
current router arguments with the parameters from the event accordingly. Then we call
oRouter.navTo("employeeOverview",this._oRouterArgs, true) with the updated router arguments to
persist the new sorting parameters in the URL. Both the previous arguments (i.e. search) and the new arguments
for the sorting will then be handled by the matched event handler for the employeeOverview route.
Congratulations! Even the sorting options of the table can now be bookmarked. Try to access the following pages:
● webapp/index.html#/employees/overview?sortField=EmployeeID&sortDescending=true
● webapp/index.html#/employees/overview?
search=an&sortField=EmployeeID&sortDescending=true
When changing the table’s sorting options, you will see that the hash updates accordingly.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
335
Step 14: Make Dialogs Bookmarkable
In this step, we want to allow bookmarking of the dialog box that is opened when the user clicks the Sort button.
The dialog should automatically open when the URL contains the query parameter showDialog.
Preview
Figure 93: Bookmark for a dialog
Coding
You can view and download all files in the Samples in the Demo Kit at Routing and Navigation - Step 14.
/controller/employee/overview/EmployeeOverviewContent.controller.js
sap.ui.define([
336
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
"sap/ui/demo/nav/controller/BaseController",
"sap/ui/model/Filter",
"sap/ui/model/FilterOperator",
"sap/ui/model/Sorter"
], function (BaseController, Filter, FilterOperator, Sorter) {
"use strict";
return
BaseController.extend("sap.ui.demo.nav.controller.employee.overview.EmployeeOverview
Content", {
onInit: function () {
...
},
_onRouteMatched : function (oEvent) {
// save the current query state
this._oRouterArgs = oEvent.getParameter("arguments");
this._oRouterArgs.query = this._oRouterArgs["?query"] || {};
delete this._oRouterArgs["?query"];
if (this._oRouterArgs.query) {
// search/filter via URL hash
this._applySearchFilter(this._oRouterArgs.query.search);
// sorting via URL hash
this._applySorter(this._oRouterArgs.query.sortField,
this._oRouterArgs.query.sortDescending);
// show dialog via URL hash
if (!this._oRouterArgs.query.showDialog) {
this._oVSD.open();
}
}
},
onSortButtonPressed : function (oEvent) {
var oRouter = this.getRouter();
this._oRouterArgs.query.showDialog = 1;
oRouter.navTo("employeeOverview",this._oRouterArgs);
},
...
_initViewSettingsDialog : function () {
var oRouter = this.getRouter();
this._oVSD = new sap.m.ViewSettingsDialog("vsd", {
confirm: function (oEvent) {
var oSortItem = oEvent.getParameter("sortItem");
this._oRouterArgs.query.sortField = oSortItem.getKey();
this._oRouterArgs.query.sortDescending =
oEvent.getParameter("sortDescending");
delete this._oRouterArgs.query.showDialog;
oRouter.navTo("employeeOverview",this._oRouterArgs, true /
*without history*/);
}.bind(this),
cancel : function (oEvent){
delete this._oRouterArgs.query.showDialog;
oRouter.navTo("employeeOverview",this._oRouterArgs, true /
*without history*/);
}.bind(this)
});
...
},
...
});
});
Once again we will update the EmployeeOverviewContent controller to add support for the bookmarking of our
sorting dialog. We decide to choose a query parameter showDialog that controls if the dialog is opened directly
when we navigate to the page with a deep link. Therefore, we extend the matched event handler for the
employeeOverview route. If the query parameter showDialog is set to 1 (note the implicit conversion to a
Boolean type for the check) we open the dialog. We only have to make sure that the dialog does not get closed
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
337
again by the router as this behavior is the default when navigating. Therefore, we call
oEvent.preventDefault() to tell the router that we want to keep the dialog open.
Next we change the press handler of the sort button. In the onSortButtonPressed function we set
this._oRouterArgs.query.showDialog = 1 and call navTo() to let the router do the job instead of directly
opening the dialog. Finally, we delete this._oRouterArgs.query.showDialog before calling navTo() in the
confirm and cancel event handlers of the ViewSettingsDialog. This is important to make sure that the dialog
does not open again by the matched handler.
We are now done with this step. Try to access the following pages:
● webapp/index.html#/employees/overview?showDialog=1
● webapp/index.html#/employees/overview?
search=an&sortField=EmployeeID&sortDescending=true&showDialog=1
As you can see, the dialog opens automatically if the parameter showDialog=1 is added to the URL. That’s
exactly what we wanted.
Step 15: Reuse an Existing Route
The Employees table displays employee data. However, the resumes of the employees are not accessible from this
view yet. We could create a new route and a new view to visualize the resume again, but we could also simply reuse
an existing route to cross-link the resume of a certain employee. In this step, we will add a feature that allows users
to directly navigate to the resume of a certain employee. We will reuse the Resume page that we have created in an
earlier step. This example illustrates that there can be multiple navigation paths that direct to the same page.
338
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Preview
Figure 94: Navigation to an existing route from a table item
Coding
You can view and download all files in the Samples in the Demo Kit at Routing and Navigation - Step 15.
webapp/view/employee/overview/EmployeeOverviewContent.view.xml
<mvc:View
controllerName="sap.ui.demo.nav.controller.employee.overview.EmployeeOverviewContent
"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Table id="employeesTable"
items="{/Employees}"
itemPress="onItemPressed">
<headerToolbar>
...
</headerToolbar>
<columns>
...
</columns>
<items>
<ColumnListItem type="Active">
<cells>
...
</cells>
</ColumnListItem>
</items>
</Table>
</mvc:View>
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
339
In the EmployeeOverviewContent view we register an event handler for the itemPress event and set the type
attribute of the ColumnListItem to Active so that we can choose an item and trigger the navigation.
webapp/controller/employee/overview/
EmployeeOverviewContent.controller.js
sap.ui.define([
"sap/ui/demo/nav/controller/BaseController",
"sap/ui/model/Filter",
"sap/ui/model/FilterOperator",
"sap/ui/model/Sorter"
], function (BaseController, Filter, FilterOperator, Sorter) {
"use strict";
return
BaseController.extend("sap.ui.demo.nav.controller.employee.overview.EmployeeOverview
Content", {
...
_syncViewSettingsDialogSorter : function (sSortField, bSortDescending) {
// the possible keys are: "EmployeeID" | "FirstName" | "LastName"
// Note: no input validation is implemented here
this._oVSD.setSelectedSortItem(sSortField);
this._oVSD.setSortDescending(bSortDescending);
},
onItemPressed : function (oEvent) {
var oItem, oCtx, oRouter;
oItem = oEvent.getParameter("listItem");
oCtx = oItem.getBindingContext();
this.getRouter().navTo("employeeResume",{
employeeId : oCtx.getProperty("EmployeeID"),
query : {
tab : "Info"
}
});
}
});
});
Next we add the itemPress handler onItemPressed to the EmployeeOverviewContent controller. It reads
from the binding context which item has been chosen and navigates to the employeeResume route. We have
already added this route and the corresponding target in a previous step and can now reuse it. From now on it is
possible to navigate to the employeeResume route from our employee table as well as from the employee detail
page created in an earlier step (the route name is employee).
Step 16: Handle Invalid Hashes by Listening to Bypassed Events
So far we have created many useful routes in our app. In the very early steps we have also made sure that a Not
Found page is displayed in case the app was called with an invalid hash. Now, we proceed further and track invalid
hashes to be able to detect and correct any invalid links or add new URL patterns that are often requested but not
found. Therefore, we simply listen to the bypassed events
340
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Preview
Figure 95: Console output for invalid hashes when listening to bypassed events
Coding
You can view and download all files in the Samples in the Demo Kit at Routing and Navigation - Step 16.
webapp/controller/App.controller.js
sap.ui.define([
"sap/ui/demo/nav/controller/BaseController"
], function (BaseController) {
"use strict";
return BaseController.extend("sap.ui.demo.nav.controller.App", {
onInit: function () {
// This is ONLY for being used within the tutorial.
// The default log level of the current running environment may be
higher than INFO,
// in order to see the debug info in the console, the log level needs
to be explicitly
// set to INFO here.
// But for application development, the log level doesn't need to be
set again in the code.
jQuery.sap.log.setLevel(jQuery.sap.log.Level.INFO);
var oRouter = this.getRouter();
oRouter.attachBypassed(function (oEvent) {
var sHash = oEvent.getParameter("hash");
// do something here, i.e. send logging data to the backend for
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
341
analysis
// telling what resource the user tried to access...
jQuery.sap.log.info("Sorry, but the hash '" + sHash + "' is
invalid.", "The resource was not found.");
});
oRouter.attachRouteMatched(function (oEvent){
var sRouteName = oEvent.getParameter("name");
// do something, i.e. send usage statistics to backend
// in order to improve our app and the user experience (BuildMeasure-Learn cycle)
jQuery.sap.log.info("User accessed route " + sRouteName + ",
timestamp = " + new Date().getTime());
});
}
});
});
All we need to do is listen to the bypassed event on the router. If the bypassed event is triggered, we simply get the
current hash and log a message. In an actual app this is probably the right place to add some application analysis
features, i.e. sending analytical logs to the back end for later evaluation and processing. This could be used to
improve the app, for example, to find out why the user called the app with an invalid hash.
Note
We have chosen to place this piece of code into the App controller because this is a global feature of the app.
However, you could also place it anywhere else, for example in the NotFound controller file or in a helper
module related to analysis.
Now try to access webapp/index.html#/thisIsInvalid while you have your browser console open. As you
can see, there is a message that issues a faulty hash. Furthermore, our NotFound page is displayed.
Related Information
API Reference: sap.m.routing.Router
Step 17: Listen to Matched Events of Any Route
In the previous step, we have listened for bypassed events to detect possible technical issues with our app. In this
step, we want to improve the analysis use case even more by listening to any matched event of the route. We could
use this information to measure how the app is used and how frequently the pages are called. Many Web analytic
tools track page hits this way. The collected information can be used, for example to improve our app and its
usability.
342
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Preview
Figure 96: Console output for routes matched by listening to routeMatched events
Coding
You can view and download all files in the Samples in the Demo Kit at Routing and Navigation - Step 17.
webapp/controller/App.controller.js
sap.ui.define([
"sap/ui/demo/nav/controller/BaseController"
], function (BaseController) {
"use strict";
return BaseController.extend("sap.ui.demo.nav.controller.App", {
onInit: function () {
var oRouter = this.getRouter();
oRouter.attachBypassed(function (oEvent) {
var sHash = oEvent.getParameter("hash");
// do something here, i.e. send logging data to the back end for
analysis
// telling what resource the user tried to access...
jQuery.sap.log.info("Sorry, but the hash '" + sHash + "' is
invalid.", "The resource was not found.");
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
343
});
oRouter.attachRouteMatched(function (oEvent){
var sRouteName = oEvent.getParameter("name");
// do something, i.e. send usage statistics to back end
// in order to improve our app and the user experience (BuildMeasure-Learn cycle)
jQuery.sap.log.info("User accessed route " + sRouteName + ",
timestamp = " + new Date().getTime());
});
}
});
});
We extend the App controller again and listen to the routeMatched event. The routeMatched event is thrown for
any route that matches to our route configuration in the descriptor file. In the event handler, we determine the
name of the matched route from the event parameters and log it together with a time stamp. In an actual app, the
information could be sent to a back-end system or an analytics server to find out more about the usage of your
app.
Now you can access, for example, webapp/index.html#/employees while you have the console of the browser
open. As you can see, there is a message logged for each navigation step that you do within the app.
Testing
In this tutorial we will test application functionality with the testing tools that are delivered with OpenUI5. At
different steps of this tutorial you will write tests using QUnit, OPA5, and the mock server. Additionally, you will
learn about testing strategies, Test Driven Development (TDD), and much more.
For the application features that we add, we focus on writing clean and testable code with the goal of having good
test coverage and a high quality app. We will create a simple full screen app that we will extend with more tests and
features throughout the tutorial.
Imagine the following situation: You and your development team take over a bulletin board prototype that will be
shipped as a product soon. A bulletin board typically consists of functionality to browse posts and add own offers
to the board. However, the prototype only covers a minimum set of features and tests so far.
With this very minimalistic app as a starting point, we have a good foundation and we can inspect the most
important testing functionality. Furthermore, we want to implement new features for the app that were requested
by the product team using Test Driven Development and best practices for writing testable code and testing
OpenUI5 apps.
So why do we do all this? Obviously, writing tests and testable code does not come without effort. Well, we want to
ensure the implementation of a high quality app by having decent test coverage of our application logic. And we
check that our code does not break by running the automated tests whenever we change something or when we
upgrade to a newer version of the OpenUI5 framework or other external libraries. Additionally, we can find bugs
proactively and do not need excessive manual testing anymore so the efforts definitely pay off. Also, when we
decide to refactor something in the future, we can easily verify that the features of the app are still working as
expected.
There are a lot more reasons and many small details that we will address throughout this tutorial. You can work
yourself through the steps by applying the code deltas individually or by downloading the samples for each step
and playing around with it.
344
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Preview
Prerequisites
In addition to the prerequisites that are presupposed for all our tutorials (see Prerequisites [page 38]), you should
also be familiar with the basics of JavaScript unit testing with QUnit. Have a look at the official QUnit
documentation to make yourself familiar with basic testing knowledge. Steps 27 to 29 of the Walkthrough tutorial
also cover the test setup in an app that is used throughout this tutorial.
If you want to automate the test execution using a test runner, you can set this up as described under Test
Automation [page 899].
Tip
You don't have to do all tutorial steps sequentially, you can also jump directly to any step you want. Just
download the code from the previous step, and start there.
You can view and download the files for all steps in the Demo Kit at Testing Apps. Copy the code to your
workspace and make sure that the application runs by calling the webapp/test/test.html file. Depending on
your development environment you might have to adjust resource paths and configuration entries.
For more information check the following sections of the tutorials overview page (see Get Started: Setup and
Tutorials [page 38]):
● Downloading Code for a Tutorial Step [page 40]
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
345
● Adapting Code to Your Development Environment [page 40]
Related Information
Testing [page 852]
QUnit Home Page
Step 1: Overview and Testing Strategy
In this step, we will take a look at the prototype and define the test strategy for our app. The prototype already
contains the infrastructure for unit and integration testing and a minimum set of tests and features.
Note
In this tutorial we will focus on writing clean unit and integration tests for apps. They build the foundation and
are crucial for good application quality. We will also outline how to write testable code. Not all implementation
patterns can be tested easily, but when writing the test code together with the implementation code as we have
in this tutorial, testable code is a natural result.
346
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Preview
Figure 97: The prototype app
Coding
To set up your project for this tutorial, download the files for Step 1 from the Samples in the Demo Kit at Testing Step 1. Copy the code to your workspace and make sure that the application runs by calling the webapp/test/
mockServer.html file.
Depending on your development environment, you might have to adjust resource paths and configuration entries.
The project structure and the files provided with this tutorial are explained in detail in the Walkthrough [page 86]
tutorial.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
347
After downloading Step 1, you should have the following files:
Figure 98: Folder structure with downloaded files
The Initial App
With the downloaded code, you now have the bulletin board prototype, set up according to the OpenUI5 best
practices. The prototype provides the common features of an OpenUI5 app. If you have completed the
Walkthrough tutorial, you should be familiar with most of the source code in this step. Additional features of the
app are:
● Entry Page
In this tutorial, we will often switch between testing application features manually, and running automated
tests. The webapp/test/test.html file provides a list of entry points for the app so that you do not have to
enter the URLs manually. From this page you can open the app with mock data, run the unit tests, run the
integration tests, or run the app’s test suite (this will be added later in the tutorial). Note that in a productive
348
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
scenario we would have an additional entry point that calls the app with a real service. At this stage we are
working with mock data and don't have a real service for our prototype yet, so we have left this step out.
● Home Page
The home page of our bulletin board app is the webapp/test/mockServer.html file. On this page, we
initialize OpenUI5, start the mock server, and instantiate our app component. It consists of a single view that
displays a list of posts from a bulletin board with several attributes in a table.
Note
We do not yet have a real service for the bulletin board prototype so run the app with mock data and this
test page throughout the tutorial. The mock server helps by mimicking a real service and it processes
requests with a small delay, just as a real service would. This is perfect for realistic application testing and is
also helpful for local development tests. It is a good practice to put all test pages in the test folder of the
app, so that they are clearly separated from the productive coding.
● Data
In the webapp/localService/ folder, you can find the metadata and the mock data for the app. The
metadata.xml file is used by the mock server to simulate real back-end service calls in the app. It describes
our OData service and you can replace it later with a real service. The service we use has a single OData entity:
○ Post
A post consists of typical properties like Title, Description, and Price. Each post is assigned to a Category
and a Contact. The entity can be identified with its ID property: PostID. The corresponding EntitySet is
Posts.
○ Category
In our example, the category only has a Name property. Posts are sorted into a category by the category
name. The corresponding EntitySet is Categories.
○ Comment
A comment has an Author, a Date, and a CommentText property. The entity can be identified by the
CommentID property and is linked to a post by the ParentID. The corresponding EntitySet is
Comments.
The actual test data containing several mock posts is located in the webapp/test/service/posts.json
file.
● Testing Functionality
The team that created the first prototype already took care of the basic test setup. Everything required for
application testing is shipped with OpenUI5 and can simply be used within the app. The testing infrastructure
is set up in the test folder that is located in the webapp folder of the app:
○ Mock Server
The mock server is set up in the webapp/localService/mockserver.js file. It loads the metadata and
the mock data in the same folder. Using the mock server allows us to run the app easily and show realistic
data for testing, even without a network connection and without the need of having a remote server for our
application data.
There is a configurable delay for each request that is processed by the mock server that allows you to
mimick a slow back-end server.
○ Unit Tests
All unit tests are located in the webapp/test/unit folder and can be started by calling the
unitTests.qunit.html file in the same folder. Initially, there are only a few tests for model instantiation
and formatters that cover basic functionality in the prototype. We will give you more details about the unit
test setup later in the tutorial.
○ Integration Tests
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
349
Integration tests are written in OPA5 – a tool for integration testing that is included in OpenUI5 – and can
be found in the webapp/test/integration folder. You can start all OPA5 tests by calling the
opaTests.qunit.html file in the same folder. OPA5 tests are organized in test journeys, and we have
included a worklist journey that checks if the table of posts is displayed properly. We will give you more
details about the integration test setup later in the tutorial.
● Other quality-related features of the app
The app is set up according to best practices and already contains many helpful features.
○ Separation of concerns (MVC)
All artifacts are located in either the model, view, or controller folder of the app. The app’s component
and its descriptor configure which of those MVC artifacts to load. This configuration controlls the
navigation flow of the app.
○ Separation of productive and nonproductive code
All nonproductive code is located in the test subfolder. This includes the unit and integration tests, and
the test page to call the app with mock data. All productive code is located in the webapp folder. This
clearly separates the test artifacts from the application coding and makes it easy to remove all test-related
artifacts before deploying the app for productive use.
○ Busy handling
As a best practice, you should always give users instant feedback when triggering actions and navigating
in the app. The app already includes functionality to display a busy indication when data is loaded or
actions are triggered. To simulate a slow backend and show the behavior of the app, the mock server is
configured with a delay of one second for each request.
Now that we have a running prototype, we can further extend it with additional tests and features. Make sure that
the app is running by calling the test page, the unit tests, and the integration tests from the entry page webapp/
test/test.html. The app should display a list of bulletin board posts as seen in the screenshot above and the
tests should run without errors.
Test Strategy
Let’s first take a look at best practices for testing apps written in OpenUI5. JavaScript is a dynamic programming
language and only some issues can be detected by static code check tools and manual testing. Automated tests
that execute the code regularly are beneficial for good quality and development productivity – especially when
you're developing in short development cycles.
We expect our prototype to be released and shipped as a product soon, so we need a solid testing strategy.
Fortunately the prototype team has already thought ahead and prepared an infrastructure for unit and integration
testing that is included in the app. This is a really good starting point for further enhancements of the app.
The mock server is also set up and allows us to test the app with local test data instead of a real back-end service.
We can use the mock data for writing reliable integration tests that do not depend on another system which might
be unavailable when the tests are run.
Note
If you start developing an app from scratch, you should always consider testing from the very beginning of the
software life cycle. Nobody wants to write tests for undocumented code and make assumptions about the logic.
It is worth the effort to think about code checks, unit and integration testing, and a solid testing strategy from
the very start.
350
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Before you start implementing your first test, you should think about how to test the different aspects of your
application. The image below shows the testing tools along the agile testing pyramid.
Figure 99: Testing pyramid
When you set up application testing, you should automate as many testing steps as possible. If you immediately
write a test for all the features that we implement, then you can greatly reduce manual testing efforts that are time
consuming and cumbersome. If you change something later, you can simply run the existing tests and see if the
functionality is still working as expected.
OpenUI5 comes with two testing tools: QUnit for unit testing and OPA5 for integration testing. The unit tests are
the foundation of our testing pyramid and they should validate the most important logic of our app. In addition, you
can write integration tests for more interaction-related functionality, such as interacting with UI elements of the
app.
There might still be features that are hard to test with these client-side testing frameworks. Certain features might
require a more sophisticated system test, such as a screenshot comparison that can be implemented with
additional testing frameworks. And of course, you should also schedule manual tests (for example, browser,
performance, or security tests) to make sure that the app is behaving as expected.
Conventions
● Write unit tests in QUnit for more logic-related functionality
● Write integration tests in OPA5 for user interaction
● Separate productive and nonproductive code in the app (webapp, test folder)
● Provide a local test page that triggers the app in test mode with mock data (test/mockServer.html)
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
351
Related Information
App Templates: Kick Start Your App Development [page 1011]
Worklist Template [page 1012]
Testing [page 852]
Unit Testing with QUnit [page 853]
Integration Testing with One Page Acceptance Tests (OPA5) [page 871]
Mock Server [page 891]
Walkthrough [page 86]
Step 2: A First Unit Test
In this step we will analyze the unit testing infrastructure and write a first unit test.
The product team requested a feature to highlight the price with colors depending on the amount. This can be
done using the standard semantic colors that are defined for states like Success, Warning, or Error.
The price values can be mapped to semantic states as follows:
● price < 50: Status is green (Success)
● price >= 50 and price < 250: Status is normal (None)
● price >= 250 and price < 2000: Status is orange (Warning)
● price >= 2000: Status is red (Error)
As we use Test Driven Development (TDD) we define the test case first, before we actually implement the feature.
So we will now start by implementing a test for the Price State feature. Naturally the test will fail until the feature is
implemented in the next step.
Note
Test Driven Development (TDD) is a software development model that relies on a very short development cycle.
When using TDD a developer first writes a failing automatic test case to describe the behavior of a new feature
or functionality. As soon as the test fails (due to the still missing implementation) the role of the developer
switches to the implementation. The code is added to make the test run successful and then the cycle starts
over again.
There might also be iterations where just the implementation or testing code is refactored to make it more
elegant. TDD reduces complexity while maintaining high test coverage of the application coding at the same
time.
352
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Preview
Figure 100: The unit test will initially fail as the implementation is not provided yet
Unit Test Setup
All unit tests are located in the webapp/test/unit folder and can be started manually by calling the
unitTests.qunit.html file in the same folder or the entry page. This HTML page is a QUnit runner that calls all
unit tests of the app and displays the test results in a readable format.
Note
Some testrunners like Karma do not require an HTML page to invoke the tests but work with configuration files
instead. They can directly invoke the allTests.js file and log the test results in their own format. Therefore
we make sure that the allTests.js file does not contain any UI output and just calls the various test cases of
the app.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
353
Figure 101: Unit test infrastructure in the application
Let’s take a closer look at the unitTests.qunit.html file. The application root is stored in the webapp folder
two levels above. In the bootstrap tag of the HTML page we define two namespaces to refer to the app and the
unit tests. The namespace of the unit tests points to the current folder as all test artifacts are located below the
current folder:
● sap.ui.demo.bulletinboard: "../../"
● test.unit: "./"
The namespace abstraction allows us to refer to all application and testing parts without having to use the full
path. Furthermore, all unit tests are put in a similar folder structure and get the same name as the artifact that is
tested. For example, the tests for the file webapp/model/formatter.js are located in the webapp/test/unit/
model/formatters.js folder. For more details on the unit test setup please have a look at the coding of the
prototype.
Coding
You can view and download all files in the Samples in the Demo Kit at Testing Apps - Step 2.
webapp/model/formatter.js
sap.ui.define([
"sap/m/Text"
], function (Text) {
"use strict";
return {
354
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
});
};
numberUnit: function (sValue) {
…
},
priceState: function () {
}
First we think about the feature that we want to implement. We want to introduce a new state for the price, and its
value should depend on certain price ranges. OpenUI5 controls typically have semantic states like Success,
Warning, or Error. We will need this formatter function to convert the numeric price value from the model to a state
value for the control. But without caring too much about the actual implementation of this formatter we just add
an empty function priceState to the formatter file for now and focus on the unit tests first.
webapp/test/unit/model/formatter.js
sap.ui.require(
[
"sap/ui/demo/bulletinboard/model/formatter"
],
function (formatter) {
"use strict";
QUnit.module("Number unit");
…
QUnit.module("Price State");
function priceStateTestCase(oOptions) {
// Act
var sState = formatter.priceState(oOptions.price);
// Assert
oOptions.assert.strictEqual(sState, oOptions.expected, "The price state
was correct");
}
QUnit.test("Should format the products with a price lower than 50 to
Success", function (assert) {
priceStateTestCase.call(this, {
assert: assert,
price: 42,
expected: "Success"
});
});
QUnit.test("Should format the products with a price of 50 to Normal",
function (assert) {
priceStateTestCase.call(this, {
assert: assert,
price: 50,
expected: "None"
});
});
QUnit.test("Should format the products with a price between 50 and 250 to
Normal", function (assert) {
priceStateTestCase.call(this, {
assert: assert,
price: 112,
expected: "None"
});
});
QUnit.test("Should format the products with a price between 250 and 2000 to
Warning", function (assert) {
priceStateTestCase.call(this, {
assert: assert,
price: 798,
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
355
});
expected: "Warning"
});
QUnit.test("Should format the products with a price higher than 2000 to
Error", function (assert) {
priceStateTestCase.call(this, {
assert: assert,
price: 2001,
expected: "Error"
});
});
}
);
Now we write tests that call the function we have just defined and check for the correct result when passing in
various arguments.
By writing these tests, we actually implement the following specification in our tests that was defined by the
product team.
● price < 50: Status is green (Success)
● price >= 50 and price < 250: Status is normal (None)
● price >= 250 and price < 2000: Status is orange (Warning)
● price >= 2000: Status is red (Error)
Whenever we run the tests, we will implicitly check that the feature is still working as it was designed. To keep it
simple, we should only write a minimum set of tests that cover the most important cases, but also including edge
cases like the value 50 or unexpected values.
Let’s have a look at the implementation of the unit tests now: We add our unit tests to the webapp/test/unit/
model/formatter.js file. The path below the app and the test folder is similar so it can easily associate the test
with the tested functionality. There are already formatter functions for the number unit conversion defined in the
code - you can have a quick look before we add our own tests.
We add a new QUnit module for our price state tests after the number unit conversion tests. We could write a test
checking the result of the formatter for each of these cases but we do not want to repeat ourselves (“DRY”) –
neither in the tests nor in the application coding – so we create a reuse function called priceStateTestCase. In
this function, we call the formatter with the arguments provided as oOptions and make a strictEqual assertion
for the expected parameter.
Note
There must be at least one assertion per QUnit test. If the actual value matches the expected value then the test
is successful. However, if there are more assertions in a test case and a subsequent assertion fails, the whole
test fails with the error message of the failed assertion.
There are also other types of assertions, for example the ok assertion that does not check the type. For more
details, have a look at the official QUnit documentation.
The assert object – a special object injected by QUnit – is passed on as a reference to the function. QUnit is loaded
once for the whole unit testing part of the app.
356
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Note
The main page for calling the unit tests is webapp/test/unit/unitTests.qunit.html. In this file we load
the QUnit runtime and an allTests.js file that loads and directly executes all files with unit tests. The other
content of this file is just HTML for displaying the QUnit test result page.
And now for the actual test cases: Whenever we want to start a new test we call QUnit.test with a test
description and a callback function containing the test logic as an argument. The callback is invoked with a special
assert object that is maintained by QUnit. We can simply call assertions as we saw above.
Inside each test we simply call our reuse function with different parameters for the price and the expected state
that reflect our specification above. With five tests we can check the most important cases for our price state
converter. There are four tests for the four different states and one edge case test with the value 50, that makes
sure that the correct state is chosen.
That’s it, you just wrote your first unit test. When you call the webapp/test/unit/unitTests.qunit.html file
in your browser, you can see that the first module for the number unit formatter is still green but our price state
tests are red and failing. The error message tells us that the result of the empty formatter function is not as
expected.
TDD methodology tells us to do the implementation as soon as the test fails and to come back to testing as soon
as the tests are successful again. You run the unit tests after each code change, and you're done when the test
does not fail anymore. We now switch to the implementation part and define the details of the formatter function in
the next step.
Conventions
● Write unit tests for testing the logical correctness of your features
Related Information
Unit Testing with QUnit [page 853]
QUnit Home Page
Step 3: Adding the Price Formatter
We will now take care of the implementation of the price formatter and make sure that the tests we wrote in the
previous step run successfully.
If the tests are passed, we can be sure that the formatter is formally correct but it is still not visible in the app. So
additionally, we will add the formatter to the UI to be able to verify and check that the price is shown properly.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
357
Preview
Figure 102: The price is now formatted with a semantic color
Coding
You can view and download all files in the Samples in the Demo Kit at Testing - Step 3.
webapp/model/formatter.js
sap.ui.define([
"sap/m/Text"
], function (Text) {
"use strict";
return {
358
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
});
};
numberUnit: function (sValue) {
…
},
/**
* Defines a value state based on the price
*
* @public
* @param {number} iPrice the price of a post
* @returns {string} sValue the state for the price
*/
priceState: function (iPrice) {
if (iPrice < 50) {
return "Success";
} else if (iPrice >= 50 && iPrice < 250) {
return "None";
} else if (iPrice >= 250 && iPrice < 2000) {
return "Warning";
} else {
return "Error";
}
}
We change the empty formatter function that we have added in the last step and add the implementation details to
it. If the implementation matches the specification embedded in our tests we are done with implementing the
formatter.
The input for the formatter is the price value from the model and the result is the state as a string value. The
actual implementation logic is quite simple and returns a semantic state value based on the price as we have seen
already in the test. There are four cases that are reflected in the if/else statements inside the formatter.
You can now run the file webapp/test/unit/unitTests.qunit.html and check if the unit tests run
successfully. You should see your new test cases on the result page. If the overall result is successful then we have
successfully implemented our first feature.
webapp/view/Worklist.view.xml
…
<ColumnListItem vAlign="Middle">
<cells>
…
<ObjectNumber
number="{
path: 'Price',
formatter: '.formatter.numberUnit'
}"
state="{
path: 'Price',
formatter: '.formatter.priceState'
}"
unit="{Currency}"/>
</cells>
</ColumnListItem>
…
We still have to apply the changes to our UI so that we can actually see the formatted price in the app. Unit tests
are typically testing the logic independent of the user interface. That is why the tests are running successfully even
though we did not adapt the UI yet.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
359
In our worklist view we simply add a state attribute to the ObjectNumber control in the columns aggregation. We
define the same data binding path as for the number, but we use our new formatter function to determine the
proper state. If you now run the webapp/test/mockServer.html file, you can see that some of the product
prices are listed in green, black, orange, and red depending on their price.
Related Information
API Reference: sap.ui.core.ValueState
API Reference: sap.m.ObjectNumber
Step 4: Testing a New Module
In the first unit test we have just extended the formatters module with a new function. Now we will write a unit test
that will test the functionality of an entirely new module.
A frequently used feature of a bulletin board is to flag interesting posts to mark them for later reading. The UI
should contain a button to toggle the flagged state for each item. We will implement this feature with a custom
type and again start writing the test case for it first and add the implementation later.
360
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Preview
Figure 103: The unit test for the Flagged feature will fail until the feature is implemented
Coding
You can view and download all files in the Samples in the Demo Kit at Testing - Step 4.
webapp/model/FlaggedType.js (new)
sap.ui.define([
"sap/ui/model/SimpleType"
], function (SimpleType) {
"use strict";
return SimpleType.extend("sap.ui.demo.bulletinboard.model.FlaggedType", {
formatValue: function () {
},
parseValue: function () {
},
validateValue: function () {
}
});
});
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
361
We plan to control a button state based on the Flagged property in the model. The button expects a Boolean
value for the pressed state. In the model, we have a binary integer representation, so we will again need conversion
logic to format the model value. And we also need a back conversion to store a state change in the model when the
user clicks the button.
A formatter function will only take care of one direction so this time we decide to implement a custom data type for
the conversions. As with the previous test, we add an empty hull for our new data type in the model folder. The
FlaggedType extends the SimpleType. Its interface provides two conversion functions and a validation function:
● formatValue: formats a model value to be displayed in the UI
● parseValue: parses a UI value to be stored in the model
● validateValue: checks a value for displaying validation errors
webapp/test/unit/model/FlaggedType.js (new)
sap.ui.require(
[
"sap/ui/demo/bulletinboard/model/FlaggedType"
],
function (FlaggedType) {
"use strict";
QUnit.module("FlaggedType - formatting");
QUnit.test("Should convert 1 to true", function (assert) {
// Act
var bFormattedValue = new FlaggedType().formatValue(1);
// Assert
assert.strictEqual(bFormattedValue , true, "The formatting conversion
was correct");
});
QUnit.test("Should convert other values to false", function (assert) {
var oFlaggedType = new FlaggedType();
// Act
var bFormattedZero = oFlaggedType.formatValue(0);
var bFormattedNegativeNumber = oFlaggedType.formatValue(-666);
// Assert
assert.strictEqual(bFormattedZero, false, "The formatting conversion
was correct");
assert.strictEqual(bFormattedNegativeNumber, false, "The formatting
conversion was correct");
});
QUnit.module("FlaggedType - parsing");
QUnit.test("Should parse false to 0", function (assert) {
// Act
var iParsedValue = new FlaggedType().parseValue(false);
// Assert
assert.strictEqual(iParsedValue, 0, "The parsing conversion matched the
input");
});
QUnit.test("Should parse true to 1", function (assert) {
// Act
var iParsedValue = new FlaggedType().parseValue(true);
// Assert
assert.strictEqual(iParsedValue, 1, "The parsing conversion matched the
input");
});
}
);
362
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
The new FlaggedType.js file matches the file name of the implementation and is put in the model subfolder of
the test/unit folder similar to the implementation under the webapp folder. By keeping the same structure for
tests and productive code, we can easily relate the tests to the implementation.
We define this testing module with sap.ui.require since we just want to load dependencies but do not want to
declare a namespace for this testing module. We load the new and still empty FlaggedType implementation as
the only dependency and declare two QUnit modules: one for formatting and one for parsing, to check both the toand back-conversion of the flagged type.
Note
We do not test the validation function of the data type as our conversion is so simple. There are no expected
validation errors that we have to take care of.
In each QUnit module we define test cases for each condition. For a Boolean conversion there are just two cases,
true and false. So we expect that the integer value 1 is converted to true and everything else to false.
Let's have a look at the first test case to see how the custom data type is invoked for testing.
As we have loaded the type as a dependency, we can just access it with the variable FlaggedType and create a
new instance of it in each test case. This time we do not create a reuse function but simply create the instance
inside the test case. On the type we manually call the function formatValue that we want to test and compare the
result to the expected value in an assertion.
In the second test case, we check all other values, we expect it to be 0 but it could be also a negative value. So we
check both cases in the same test case with a separate assertion each. Only when both assertions are fulfilled the
test will be successful.
The other test cases in the parsing module are similar and check the back conversion from Boolean value to
integer value.
webapp/test/unit/allTests.js
sap.ui.define([
"test/unit/model/models",
"test/unit/model/formatter",
"test/unit/model/FlaggedType"
], function() {
"use strict";
});
In the allTests.js file we just load the new testing module as a dependency so that it is executed automatically
whenever we execute the unit tests.
You can now call the unit tests and check the result. As in the previous step, the tests should fail with an error
message that the conversion is not correct. This is expected as we did not implement the conversion logic yet but
just the tests for it.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
363
Conventions
● Use data types if you need both formatting and parsing of a model value
● Organize the tests in the same file structure as the productive code
Related Information
Using the Data Binding Type System [page 694]
API Reference: sap.ui.model.SimpleType
API Reference: sap.ui.require
364
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Step 5: Adding a Flag Button
Now that we have implemented the conversion tests, we add the corresponding functionality and show the button
to flag a post in the app. The design team has specified that the flag feature should be implemented with a toggle
button that has a flag icon.
Preview
Figure 104: The Flag button is now added to the table
Coding
You can view and download all files in the Samples in the Demo Kit at Testing - Step 5.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
365
webapp/model/FlaggedType.js
sap.ui.define([
"sap/ui/model/SimpleType"
], function (SimpleType) {
"use strict";
return SimpleType.extend("sap.ui.demo.bulletinboard.model.FlaggedType", {
/**
* Formats the integer value from the model to a boolean for the pressed
state of the flagged button
*
* @public
* @param {number} iFlagged the integer value of the formatted property
* @returns {boolean} 1 means true, all other numbers means false
*/
formatValue: function (iFlagged) {
return iFlagged === 1;
},
/**
* Parses a boolean value from the property to an integer
*
* @public
* @param {boolean} bFlagged true means flagged, false means not flagged
* @returns {number} true means 1 , false means 0
*/
parseValue: function (bFlagged) {
if (bFlagged) {
return 1;
}
});
});
return 0;
},
/**
* Validates the value to be parsed
*
* @public
* Since there is only true and false, no client side validation is required
* @returns {boolean} true
*/
validateValue: function () {
return true;
}
Lets start with the implementation code for the FlaggedType. We now add the documentation in JSDoc format
and the implementation of the three functions of the data type to the previously empty stub:
● The formatValue function takes care of the conversion from the model to the UI. As specified in the tests, a
model value of 1 will be converted to true, everything else to false. In the implementation code, this equals
to ”iFlagged === 1”.
● Similarly, the parseValue function is called by OpenUI5 when the data is written back to the model. Here, we
convert the Boolean value to an integer again.
● The validation function always returns true in this simple case, we do not expect any validation errors for this
data type.
We call these functions of the data type in the unit tests directly. So if you now run your unit tests by calling the
webapp/test/unit/unitTests.qunit.html page, the tests should already run successfully.
366
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/view/Worklist.view.xml
…
<Table …>
…
<columns>
…
<Column width="33%" id="unitNumberColumn" hAlign="End" vAlign="Middle">
<Text text="{i18n>TableUnitNumberColumnTitle}"
id="unitNumberColumnTitle"/>
</Column>
<Column width="80px" id="flaggedColumn" demandPopin="true" vAlign="Middle"/>
</columns>
<items>
<ColumnListItem vAlign="Middle">
<cells>
…
<ObjectNumber… />
<ToggleButton
id="flaggedButton"
tooltip="{i18n>flaggedTooltip}"
icon="sap-icon://flag"
pressed="{
path: 'Flagged',
type: '.types.flagged'
}"
class="sapUiMediumMarginBeginEnd"/>
</cells>
</ColumnListItem>
</items>
</Table>
…
In the view, we add a new column and a cell for the flag feature at the end of the table. We fill the cell with a
sap.m.ToggleButton control that serves as our input control for the Flagged state. We define a flag icon in
the button, a tooltip from the resource bundle, and a layouting class to make our example complete. The control's
pressed property is bound to the Flagged field in the model. Here we also apply the custom data type that is part
of the controller.
webapp/controller/Worklist.controller.js
/*global history*/
sap.ui.define([
'sap/ui/demo/bulletinboard/controller/BaseController',
'sap/ui/model/json/JSONModel',
'sap/ui/demo/bulletinboard/model/formatter',
'sap/ui/demo/bulletinboard/model/FlaggedType'
], function (BaseController, JSONModel, formatter, FlaggedType) {
"use strict";
return BaseController.extend("sap.ui.demo.bulletinboard.controller.Worklist", {
types : {
flagged: new FlaggedType()
},
formatter: formatter,
…
});
});
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
367
The controller loads the custom data type as a dependency similar to the formatters. It is then provided as a
property of the internal variable types so that it can be accessed as .types.flagged in the view as we have
seen above.
The conversion functions that are made available when we create an instance of the type are called automatically
by OpenUI5 when needed. However, by default the back conversion to the model is not enabled, so we still need a
small change in the component.
webapp/Component.js
sap.ui.define([
…
], function (UIComponent, ResourceModel, models, Device) {
"use strict";
return UIComponent.extend("sap.ui.demo.bulletinboard.Component", {
…
init: function () {
// call the base component's init function
UIComponent.prototype.init.apply(this, arguments);
// allow saving values to the OData model
this.getModel().setDefaultBindingMode("TwoWay");
…
}
});
});
To enable the propagation of the bound view properties to the model, we need to set the model's default binding
mode to TwoWay. For an OData model the default mode is OneWay which means that properties are not written
back to the model automatically. We want to propagate the state of the button automatically to the model, when
the button for a post is clicked.
webapp/i18n/i18n.properties
#~~~ Worklist View ~~~~~~~~~~~~~~~~~~~~~~~~~~
…
#XTOL: tooltip for the flagged button
flaggedTooltip=Mark this post as flagged
…
Finally, add the new string for the button tooltip to the resource bundle file. Now we can also test the application
manually by calling the webapp/test/mockServer.html page and making sure some of the buttons are pressed
initially as reflected in the model. When we flag an item by choosing the button, the property is written back to the
model transparently.
Note
As this feature covers both conversion and interaction parts, we could also have written an integration test for it
to test the interaction part also. Feel free to add an integration test for this feature if you like, we will skip it here
to focus on unit testing in this step.
368
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Related Information
Using the Data Binding Type System [page 694]
Step 6: A First OPA Test
A bulletin board may contain many posts. We expect to have a high data load once it is officially released. Then,
there might be performance issues and long loading times if we display all entries at the same time. Therefore we
will introduce a feature that limits the initial display to 20 items. The user can then click on a more button to view
more items. As with the unit test, we start by writing an integration test for this feature and then add the
application functionality later.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
369
Preview
Figure 105: The OPA test page is waiting for more items to be loaded
Coding
You can view and download all files in the Demo Kit at Testing - Step 6.
Integration Test Setup
All integration tests are located in the webapp/test/integration folder and can be started manually by calling
the opaTests.qunit.html file in the same folder or the entry page. Similar to the unit tests, the HTML page is a
370
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
QUnit runner that calls all integration tests of the app and displays the test results in a readable format. It also
might be omitted by other testrunners. There are also two namespaces defined for the app and the integration test
folder as you have seen in the unit test setup.
We write integration tests with OPA5 – a tool that is integrated and delivered with SAPUI5. It is the short name for
One-Page Acceptance tests for SAPUI5. "One-Page" here means that OPA5 is designed for single-page Web
applications, i.e. applications that consist only of one HTML file. OPA5 runs in the same browser window as the
application to be tested and opens a local iFrame to run the tests on the app.
Note
There is also a stand-alone version of OPA5 called “OPA” available that can be used for testing any kind of
single-page Web application and that does not provide any OpenUI5-specific functionality. In this tutorial, “OPA”
always refers to OPA5. It includes functionality for easily finding and matching OpenUI5 controls as well as their
properties and aggregations.
Figure 106: Integration test infrastructure in the project
For structuring integration tests with OPA we use “journeys”. A test journey contains all test cases for a specific
view or use case, for example the navigation journey simulates user interaction with the app.
The journey uses another structuring element of OPA called “page object” that encapsulates arrangements,
actions, assertions needed to describe the journey. Typically those are related to a view in the app but there can
also be stand-alone pages for browsers or common functionality.
webapp/test/integration/WorklistJourney.js
sap.ui.require(
["sap/ui/test/opaQunit"],
function (opaTest) {
"use strict";
QUnit.module("Posts");
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
371
opaTest("Should see the table with all posts", function (Given, When, Then)
{
);
// Arrangements
Given.iStartMyApp();
//Actions
When.onTheWorklistPage.iLookAtTheScreen();
// Assertions
Then.onTheWorklistPage.theTableShouldHavePagination().
and.theTitleShouldDisplayTheTotalAmountOfItems();
}
});
opaTest("Should be able to load more items", function (Given, When, Then) {
//Actions
When.onTheWorklistPage.iPressOnMoreData();
// Assertions
Then.onTheWorklistPage.theTableShouldHaveAllEntries().
and.iTeardownMyAppFrame();
});
Let’s add our first new OPA test to the WorklistJourney.js file. We describe all test cases related to the worklist
logic. We can see that there is already a test Should see the table with all posts defined that checks if
the table contains the expected number of items. There is a function opaTest that initiates a test description and
receives a test description as the first argument as well as a callback function as the second argument. This format
is similar to the unit test function QUnit.test except for the three arguments of the callback function that are
specific to OPA.
The three objects Given, When, Then are filled by the OPA runtime when the test is executed and contain the
arrangements, actions, and assertions for the test. The "Given-When-Then" pattern is a common style for
writing tests in a readable format. To describe a test case, you basically write a user story. Test cases in this format
are easy to understand, even by non-technical people.
Let’s give it a try with our new feature that only displays 20 posts in the table initially and will load more posts when
we press a trigger button or scroll down. Here is our user story "Should see the table with all posts" and its code
representation:
● Arrangements
Define possible initial states, e.g. the app is started, or specific data exists. For performance reasons, starting
the app is usually done only in the first test case of a journey. Given.iStartMyApp();
● Actions
Define possible events triggered by a user, e.g. entering some text, clicking a button, navigating to another
page. When.onTheWorklistPage.iPressOnMoreData();
● Assertions
Define possible verifications, e.g. do we have the correct amount of items displayed, does a label display the
right data, is a list filled. At the end of the test case, the app is destroyed again. This is typically done only once
in the last test case of the journey for performance reasons.
Then.onTheWorklistPage.theTableShouldHaveAllEntries ().and.iTeardownMyAppFrame();
Please also note that you have to move the and.iTeardownMyAppFrame() concatenation from the previous
opaTest function and put it at the end of the last test of a journey, in this case this is our new test. For
performance reasons, we only start and destroy the app once per journey, as it takes several seconds to construct
the iFrame and load the app. You can concatenate actions and assertions with the OPA helper object and in an
easily readable way. The functions will be executed one after another.
Now you might wonder where all those descriptive functions and the helper object onTheWorklistPage are
coming from. The answer is simple, the onTheWorklistPage object is a structuring element of OPA and inside
we will implement the actions and assertions used in this test.
372
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/test/integration/pages/Worklist.js
sap.ui.require([
'sap/ui/test/Opa5',
'sap/ui/test/matchers/AggregationLengthEquals',
'sap/ui/test/matchers/PropertyStrictEquals',
'sap/ui/demo/bulletinboard/test/integration/pages/Common',
'sap/ui/test/actions/Press'
],
function (Opa5, AggregationLengthEquals, PropertyStrictEquals, Common, Press) {
"use strict";
var sViewName = "Worklist",
sTableId = "table";
Opa5.createPageObjects({
onTheWorklistPage: {
baseClass: Common,
actions: {
iPressOnMoreData: function () {
// Press action hits the "more" trigger on a table
return this.waitFor({
id: sTableId,
viewName: sViewName,
actions: new Press(),
errorMessage: "The Table does not have a trigger"
});
}
},
assertions: {
theTableShouldHavePagination: function () {
return this.waitFor({
id: sTableId,
viewName: sViewName,
matchers: new AggregationLengthEquals({
name: "items",
length: 20
}),
success: function () {
Opa5.assert.ok(true, "The table has 20 items on the
first page");
},
errorMessage: "Table does not have all entries."
});
},
theTableShouldHaveAllEntries: function () {
return this.waitFor({
id: sTableId,
viewName: sViewName,
matchers: new AggregationLengthEquals({
name: "items",
length: 23
}),
success: function () {
Opa5.assert.ok(true, "The table has 23 items");
},
errorMessage: "Table does not have all entries."
});
},
theTitleShouldDisplayTheTotalAmountOfItems: function () {
return this.waitFor({
id: "tableHeader",
viewName: sViewName,
matchers: function (oPage) {
var sExpectedText =
oPage.getModel("i18n").getResourceBundle().getText("worklistTableTitleCount", [23]);
return new PropertyStrictEquals({
name: "text",
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
373
value: sExpectedText
}).isMatching(oPage);
},
success: function () {
Opa5.assert.ok(true, "The table header has 23
items");
the number of items: 23"
});
});
}
}
}
},
errorMessage: "The Table's header does not container
});
As you can see, the OPA page object is constructed with the call Opa5.createPageObjects and a
configuration object that contains the actions and assertions properties. It also inherits common functionality
from a Common base object that is located in the same folder and contains helper functions for starting the app.
For our test case we need to add an action iPressOnMoreData and an existing assertion
theTableShouldHaveAllEntries. OPA tests are running asynchronously, so each action and assertion starts
with a waitFor statement. The OPA run time will check and wait for the condition to be fulfilled every 400 ms by
polling. If the condition is met, the success function of the configuration is called. If the condition is still not
fulfilled after a certain amount of time (by default it is 15 seconds but this can be configured) the test will fail.
Let’s start with the action iPressOnMoreData. We define a waitFor statement with the current view and the
table. Those IDs are stored as internal variables in the require statement above and are available in all tests. OPA
will now try to find the table based on IDs. As soon as the table is available on the screen and it can be interacted
with (it is visible, not busy,...), the Press action is invoked, if not, the error message is displayed and the test fails.
When executed on a table, the Press action will simulate that a users chooses the More Data button.
Note
The Press action depends on the control that it is triggered on and has a default behavior for most UI controls.
If you, for example, execute Press on a sap.m.Page, this will trigger the Back button's Press event. This
behavior can be overridden by passing an ID as argument to the action. For more information, see the API
Reference: sap.ui.test.actions.Press.
The assertion theTableShouldHaveAllEntries is structured similarly, but it does not trigger an action. Here,
we use the success function of waitFor to assert if our application is in the expected state. This state is defined
by the matchers (in our case we expect that the list contains 23 items by using the AggregationLengthEquals.
The success function does not execute the additional checks that are needed for triggering an action. the liste
does not have to be interactable to verify that the state of the application is correct..
With this helper object we can simply check the length of the table aggregation items to the expected number of
items. We have 23 entries in our local mock data that we also use for this integration test. You can see that the
number of items is actually hard-coded in the test. So only if the table has exactly 23 items, the matcher is
evaluating to true and the assertion is passed successfully.
Note
The items in our app are served from the mock server with a slight delay so that we can see how a real service
on a backend system would behave. Even if we would have a real backend, we would purposely use the mock
server for manual testing and for using them in our test cases as the test data remains stable and unchanged.
374
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
This creates a more reliable test environment and easier tests. So we can write a test that checks exactly for 23
items here.
Now run the webapp/test/integration/opaTests.qunit.html file and make sure that the test is failing. As
you can see, the app is opened in a small iFrame on the lower right and the tests are running there. When our new
test is invoked, OPA will run into a timeout because the trigger area is not found yet. You can see more information,
if you open the developer console of your browser and check the messages in the console.
Conventions
● Use OPA tests for UI-related integration tests
● Structure OPA tests with page objects
● Use the standard matchers provided by OPA5 if possible
Related Information
Integration Testing with One Page Acceptance Tests (OPA5) [page 871]
API Reference: sap.ui.test.matchers
API Overview and Samples: sap.ui.test.Opa5
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
375
Step 7: Changing the Table to a Growing Table
Let’s switch back to developing and add the missing feature for the test we implemented in the previous step. We
will simply change the table to a growing table as this is a basic feature of the table. This will display a trigger at the
end of the table that the user can click on to display more items.
Preview
Figure 107: The List of posts is now dynamically loading more items when we scroll to the end of the page
Coding
You can view and download all files in the Samples in the Demo Kit at Testing - Step 7.
376
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/view/Worklist.view.xml
<mvc:View …
<semantic:FullscreenPage
id="page"
title="{i18n>worklistViewTitle}">
<semantic:content>
<Table
id="table"
growing="true"
width="auto"
…
>
…
</Table>
</semantic:content>
…
</semantic:FullscreenPage>
</mvc:View>
We simply set the parameter growing to true to enable our feature. Now we can run the integration test that we
just wrote in the previous step and it should not fail anymore. Similarly, if we run the app, we now see only 20 items
initially. And if we choose the More button then three more items are loaded.
Conventions
● Use OPA tests for UI-related integration tests
Related Information
Growing Feature for Table and List [page 1263]
API Reference: sap.m.Table
Step 8: Testing Navigation
So far, we have a list of posts on the home page of the app. But typically, a post comes with more details that
should be displayed on a separate detail page. We call it the post page because it displays details of a post. In this
step we will introduce a new journey to test the post page. We write tests that trigger typical navigation events with
OPA. Testing navigation greatly helps in reducing manual testing efforts as it covers a lot of testing paths. It is good
practice to cover every view of your application with at least one test, since OPA will check if an exception is
thrown. In this way you can detect critical errors very fast.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
377
Preview
Figure 108: We add an OPA test that selects an item from the table and navigates to the post page
Coding
You can view and download all files in the Demo Kit at Testing - Step 8.
webapp/test/integration/PostJourney.js (New)
sap.ui.require(
["sap/ui/test/opaQunit"],
function (opaTest) {
"use strict";
QUnit.module("Post");
opaTest("Should see the post page when a user clicks on an entry of the
list", function (Given, When, Then) {
// Arrangements
Given.iStartMyApp();
//Actions
When.onTheWorklistPage.iPressOnTheItemWithTheID("PostID_15");
// Assertions
Then.onThePostPage.theTitleShouldDisplayTheName("Jeans");
});
opaTest("Should go back to the TablePage", function (Given, When, Then) {
// Actions
When.onThePostPage.iPressTheBackButton();
// Assertions
Then.onTheWorklistPage.iShouldSeeTheTable();
});
opaTest("Should be on the post page again when browser forwards is
pressed", function (Given, When, Then) {
// Actions
When.onTheBrowser.iPressOnTheForwardButton();
// Assertions
Then.onThePostPage.theTitleShouldDisplayTheName("Jeans").
378
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
);
}
});
and.iTeardownMyAppFrame();
This new journey for the Post page introduces a test case that tests the navigation and also tests if the browser
history is in the correct state, so that the user can navigate through our app with the back and forward button of
the browser. This time, instead of adding a test we will add a new journey.
A journey represents a user’s task in our app. Journeys start with the startup of our app and end with a teardown in
the last test. We don’t write isolated tests here, since starting up the app takes a lot of time and doing it too often
slows down our test execution and feedback time considerably. If the execution speed of the tests is no problem,
you may also write isolated tests.
Our new journey consists of three user interaction steps:
1. User chooses a Post to view the details
2. User chooses the Back button on the Detail page of the Post to see the list again
3. User chooses the Forward button to revisit the details of the post
webapp/test/integration/pages/Worklist.js – action object
sap.ui.require([
'sap/ui/test/Opa5',
'sap/ui/test/matchers/AggregationLengthEquals',
'sap/ui/test/matchers/PropertyStrictEquals',
'sap/ui/test/matchers/BindingPath',
'sap/ui/demo/bulletinboard/test/integration/pages/Common',
'sap/ui/test/actions/Press'
],
function (Opa5, AggregationLengthEquals, PropertyStrictEquals, BindingPath,
Common) {
"use strict";
var sViewName = "Worklist",
sTableId = "table";
Opa5.createPageObjects({
onTheWorklistPage: {
baseClass: Common,
actions: {
…
,
iPressOnTheItemWithTheID: function (sId) {
return this.waitFor({
controlType: "sap.m.ColumnListItem",
viewName: sViewName,
matchers: new BindingPath({
path: "/Posts('" + sId + "')"
}),
actions: new Press(),
errorMessage: "No list item with the id " + sId + " was
found."
});
}
Now that we have written our spec how the navigation to the Post page is planned, we first need to implement the
"click" on a list item. To identify the item we are looking for, we use the BindingPath matcher. Doing so, we make
sure that even if the order of the items changes, we always choose the same item. The press action simulates a
user click on the item.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
379
webapp/test/integration/pages/Post.js (New)
sap.ui.require([
'sap/ui/test/Opa5',
'sap/ui/test/matchers/Properties',
'sap/ui/demo/bulletinboard/test/integration/pages/Common',
'sap/ui/test/actions/Press'
],
function (Opa5, Properties, Common, Press) {
"use strict";
var sViewName = "Post";
Opa5.createPageObjects({
onThePostPage: {
baseClass: Common,
actions: {
iPressTheBackButton: function () {
return this.waitFor({
id: "page",
viewName: sViewName,
actions: new Press(),
errorMessage: "Did not find the nav button on object
page"
});
}
},
assertions: {
theTitleShouldDisplayTheName: function (sName) {
return this.waitFor({
success: function () {
return this.waitFor({
id: "objectHeader",
viewName: sViewName,
matchers: new Properties({
title: sName
}),
success: function (oPage) {
Opa5.assert.ok(true, "was on the remembered
detail page");
},
errorMessage: "The Post " + sName + " is not
shown"
});
}
});
}
}
}
});
});
After navigating to the Post page, we need a new OPA5 Page object for the page to implement our actions and
assertions.
An OPA5 Page object is used to group and reuse actions and assertions that are related to a specific part of the
screen. For more information, see Cookbook for OPA5 [page 877].
We implement a press event on the page’s nav button and we assert that we are on the correct page by checking
the title in the object header. The nav button is retrieved via DOM reference, because the page does not offer us an
API here. Since the DOM ID is the most stable attribute, we are using this to retrieve the button.
380
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/test/integration/pages/Worklist.js – assertion object
,
iShouldSeeTheTable: function () {
return this.waitFor({
id: sTableId,
viewName: sViewName,
success: function () {
Opa5.assert.ok(true, "The table is visible");
},
errorMessage: "Was not able to see the table."
});
}
After going back, we want to move forwards again, but we need to check if the back navigation actually took place.
So we assert that we are back on our table of posts again. We achieve this with a very simple waitFor statement
just checking if the table is present.
webapp/test/integration/pages/Browser.js (New)
sap.ui.require([
'sap/ui/test/Opa5',
'sap/ui/demo/bulletinboard/test/integration/pages/Common'
],
function (Opa5, Common) {
"use strict";
Opa5.createPageObjects({
onTheBrowser: {
baseClass: Common,
actions: {
iPressOnTheForwardButton: function () {
return this.waitFor({
success: function () {
Opa5.getWindow().history.forward();
}
});
}
},
assertions: {}
}
});
});
We now implement an action that is triggered when the Forward button is chosen. Since it is not part of the
browser's UI and it could be used on any page of our application, we just declare our browser’s UI as an own OPA
page object. To simulate the Forward button, we use the history API of the browser. We have to wrap our action in
a waitFor statement. Otherwise the action would be executed before our app is started.
webapp/test/integration/AllJourneys.js
webapp/test/integration/AllJourneys.js
jQuery.sap.require("sap.ui.qunit.qunit-css");
jQuery.sap.require("sap.ui.thirdparty.qunit");
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
381
jQuery.sap.require("sap.ui.qunit.qunit-junit");
sap.ui.require([
"sap/ui/test/Opa5",
"sap/ui/demo/bulletinboard/test/integration/pages/Common",
"sap/ui/demo/bulletinboard/test/integration/pages/Worklist",
"sap/ui/demo/bulletinboard/test/integration/pages/Browser",
"sap/ui/demo/bulletinboard/test/integration/pages/Post"
], function (Opa5, Common) {
"use strict";
Opa5.extendConfig({
arrangements: new Common(),
viewNamespace: "sap.ui.demo.bulletinboard.view."
});
sap.ui.require([
"sap/ui/demo/bulletinboard/test/integration/WorklistJourney",
"sap/ui/demo/bulletinboard/test/integration/PostJourney"
], function () {
QUnit.start();
});
});
To make navigation tests complete, we add the new OPA page objects and journeys to the AllJourneys file that is
invoked by the OPA test page. We add the OPA page objects Browser and Object and start the object and the
navigation journey.
If you execute the tests now, you can see in the logs of the developer tools that OPA is waiting for the object page
to be displayed. Of course, this will not happen as it is not yet implemented. But we already have a pretty good idea
on how we will implement the feature in the next step
Related Information
API Reference: sap.ui.test.matchers.BindingPath
382
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Step 9: Adding the Post Page
Now that we have covered all kinds of tests for navigation, we introduce our Post page that shows details of a post
in the bulletin board. To achieve this, we have to introduce a new view/controller pair and adjust the routing of the
application.
Preview
Figure 109: The Post page with more details about the post
Coding
You can view and download all files in the Samples in the Demo Kit at Testing - Step 9.
webapp/manifest.json
{
"_version": "1.8.0",
…
"sap.ui5": {
…
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "sap.ui.demo.bulletinboard.view",
"controlId": "app",
"controlAggregation": "pages",
"async": true
},
"routes": [
{
"pattern": "",
"name": "worklist",
"target": "worklist"
},
{
"pattern": "Post/{postId}",
"name": "post",
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
383
}
}
}
}
"target": "post"
],
"targets": {
"worklist": {
"viewName": "Worklist",
"viewId": "worklist",
"viewLevel": 1
},
"post": {
"viewName": "Post",
"viewId": "post",
"viewLevel": 2
}
}
We have already used the #/Posts/{postId} hash in our tests and a view called the Post page, so we will now
add a route and a target to the routing configuration of the descriptor with these patterns. It is simply defining a
mandatory routing parameter postId that we fill with the ID from the model when navigating. The target
configuration references a view called Post with a view level deeper than the home page. For more information,
see the Navigation and Routing [page 269] tutorial.
webapp/view/Worklist.view.xml
<mvc:View
controllerName="sap.ui.demo.bulletinboard.controller.Worklist"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
xmlns:core="sap.ui.core"
xmlns:semantic="sap.m.semantic">
<semantic:FullscreenPage
id="page"
title="{i18n>worklistViewTitle}">
<semantic:content>
<Table …>
…
<items>
<ColumnListItem
vAlign="Middle"
type="Navigation"
press="onPress">
…
</ColumnListItem>
</items>
</Table>
</semantic:content>
…
</semantic:FullscreenPage>
</mvc:View>
We configure the table items to be of type Navigation, so a user can trigger the navigation by choosing an item.
When a press event is triggered, the onPress handler is called to navigate to the Post page.
384
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/controller/Worklist.controller.js
/*global history*/
sap.ui.define([
'sap/ui/demo/bulletinboard/controller/BaseController',
'sap/ui/model/json/JSONModel',
'sap/ui/demo/bulletinboard/model/formatter'
], function (BaseController, JSONModel, formatter) {
"use strict";
return BaseController.extend("sap.ui.demo.bulletinboard.controller.Worklist", {
…
/* =========================================================== */
/* event handlers
*/
/* =========================================================== */
…
/**
* Event handler when a table item gets pressed
* @param {sap.ui.base.Event} oEvent the table selectionChange event
* @public
*/
onPress: function (oEvent) {
this.getRouter().navTo("post", {
// The source is the list item that got pressed
postId: oEvent.getSource().getBindingContext().getProperty("PostID")
});
},
…
});
});
The press handler function instructs the router to navigate to the post pattern with the PostID from the
binding context of the currently selected item. This fills the mandatory URL parameter, navigates to the post
page, and updates the hash automatically.
webapp/view/Post.view.xml (New)
<mvc:View
controllerName="sap.ui.demo.bulletinboard.controller.Post"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
xmlns:semantic="sap.m.semantic">
<semantic:FullscreenPage
id="page"
busy="{postView>/busy}"
busyIndicatorDelay="0"
navButtonPress="onNavBack"
showNavButton="true"
title="{i18n>objectTitle}">
<semantic:content>
<ObjectHeader
id="objectHeader"
title="{Title}"
number="{
path: 'Price',
formatter: '.formatter.numberUnit'
}"
numberUnit="{Currency}"
backgroundDesign="Translucent">
</ObjectHeader>
</semantic:content>
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
385
</semantic:FullscreenPage>
</mvc:View>
We provide a minimalistic detail page showing only some fields of the selected post for now. In the test we use the
following information:
● Control with the ID page on this view
● title of the post we navigate to
● Back button to navigate back to the home page
webapp/controller/Post.controller.js (New)
sap.ui.define([
'sap/ui/demo/bulletinboard/controller/BaseController',
'sap/ui/model/json/JSONModel',
'sap/ui/demo/bulletinboard/model/formatter'
], function (BaseController, JSONModel, formatter) {
"use strict";
return BaseController.extend("sap.ui.demo.bulletinboard.controller.Post", {
formatter: formatter,
/* =========================================================== */
/* lifecycle methods
*/
/* =========================================================== */
/**
* Called when the worklist controller is instantiated.
* @public
*/
onInit: function () {
// Model used to manipulate control states. The chosen values make sure,
// detail page is busy indication immediately so there is no break in
// between the busy indication for loading the view's meta data
var oViewModel = new JSONModel({
busy: false
});
this.getRouter().getRoute("post").attachPatternMatched(this._onPostMatched, this);
this.setModel(oViewModel, "postView");
},
/* =========================================================== */
/* event handlers
*/
/* =========================================================== */
/**
* Navigates back to the worklist
* @function
*/
onNavBack: function () {
this.myNavBack("worklist");
},
/* =========================================================== */
/* internal methods
*/
/* =========================================================== */
/**
* Binds the view to the post path.
*
* @function
* @param {sap.ui.base.Event} oEvent pattern match event in route 'object'
* @private
*/
_onPostMatched: function (oEvent) {
var oViewModel = this.getModel("postView"),
oDataModel = this.getModel();
386
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
this.getView().bindElement({
path: "/Posts('" + oEvent.getParameter("arguments").postId + "')",
events: {
dataRequested: function () {
oDataModel.metadataLoaded().then(function () {
// Busy indicator on view should only be set if
metadata is loaded,
// otherwise there may be two busy indications next to
each other on the
// screen. This happens because route matched handler
already calls '_bindView'
// while metadata is loaded.
oViewModel.setProperty("/busy", true);
});
},
dataReceived: function () {
oViewModel.setProperty("/busy", false);
}
}
});
}
});
});
The controller of the Post page needs to take care of the data binding when a navigation event has happened. In
the init function of the controller we define a local view model and attach to the routing event. When the
routing event is triggered, we bind the view to the post with the specified ID.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
387
Step 10: Test Suite and Automated Testing
In this step, we will step back from our tests and application features that we have implemented so far and add
another important piece of test code: The test suite page. A test suite can execute multiple tests and collect the
results. This comes in handy for automatic tools in a continuous integration process.
Preview
Figure 110: A Selenium runner for the test suite of the bulletin board
Coding
You can view and download all files in the Samples in the Demo Kit at Testing - Step 10.
webapp/test/testsuite.qunit.html (New)
<!DOCTYPE html>
<html>
<head>
<title>QUnit TestSuite for Bulletin Board</title>
388
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
<script type="text/javascript" src="/resources/sap/ui/qunit/qunitredirect.js"></script>
<script>
/**
* Add test pages to this test suite function.
*
*/
function suite() {
var oSuite = new parent.jsUnitTestSuite(),
sContextPath = window.location.pathname.substring(0,
window.location.pathname.lastIndexOf("/") + 1);
oSuite.addTestPage(sContextPath + "unit/unitTests.qunit.html");
oSuite.addTestPage(sContextPath + "integration/opaTests.qunit.html");
return oSuite;
}
</script>
</head>
<body>
</body>
</html>
The coding is quite straight-forward, we require the relevant QUnit files for redirecting to the central test suite and
provide a configuration function suite() that is called automatically by the testrunner.
Inside this function we add the QUnit pages for the app’s unit and integration tests. For technical reasons, we have
to provide an absolute path to the HTML pages so that the testrunner can execute them centrally. You can now run
the webapp/test/testsuite.qunit.html file to check if all unit and integration tests are running fine with one
URL.
Note
A similar test suite can be configured as a pre-commit hook in local build environments or as a pre-submit hook
in a continuous integration scenario on the central build server. Only when all tests run successfully, a new
change is accepted and may be merged.
Alternatively you can use a local test runner, such as Selenium or Karma, that automatically executes all tests
whenever a file in the app project has been changed. All of these configurations run the tests and collect the
resulting messages for further analysis. Therefore, it is very important to define meaningful test descriptions
and success as well as error messages as you write your application tests.
Conventions
● Create a test suite app that triggers all your tests at once
● Run the test suite whenever you change the code of the app
Related Information
Karma Home Page
Selenium Home Page
Test Automation [page 899]
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
389
Step 11: Testing User Input
In this step, we will write a test that simulates a user search. We will enter the search string into the search field and
check if the correct results are shown in worklist table.
Preview
Figure 111: Testing user input in a search field
Coding
You can view and download all files in the Demo Kit at Testing - Step 11.
test/integration/WorklistJourney.js
sap.ui.require(
["sap/ui/test/opaQunit"],
function (opaTest) {
"use strict";
QUnit.module("Posts");
opaTest("Should see the table with all Posts", function (Given, When, Then)
{
// Arrangements
Given.iStartMyApp();
//Actions
When.onTheWorklistPage.iLookAtTheScreen();
// Assertions
Then.onTheWorklistPage.theTableShouldHavePagination().
and.theTitleShouldDisplayTheTotalAmountOfItems();
});
390
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
opaTest("Should be able to load more items", function (Given, When, Then) {
//Actions
When.onTheWorklistPage.iPressOnMoreData();
// Assertions
Then.onTheWorklistPage.theTableShouldHaveAllEntries();
});
opaTest("Should be able to search for items", function (Given, When, Then) {
//Actions
When.onTheWorklistPage.iSearchFor("Bear");
);
}
});
// Assertions
Then.onTheWorklistPage.theTableHasOneItem().
and.iTeardownMyAppFrame();
In this example, we extend the WorklistJourney.js file with a new test "Should be able to enter text
into the search field". The action within this test simulates a user entering text into a search field, so we
pass a search string "Bear" to this action. It is important to move the Teardown step to the last test, otherwise
our app would be destroyed and the test would not be able to find the Statistics tab.
Delete .and.iTeardownMyAppFrame(); from the previous test in the file and add the new test case.
test/integration/pages/Worklist.js
sap.ui.require([
'sap/ui/test/Opa5',
'sap/ui/test/matchers/AggregationLengthEquals',
'sap/ui/test/matchers/PropertyStrictEquals',
'sap/ui/test/matchers/BindingPath',
'sap/ui/demo/bulletinboard/test/integration/pages/Common',
'sap/ui/test/actions/Press',
'sap/ui/test/actions/EnterText'
],
function (Opa5,
AggregationLengthEquals,
PropertyStrictEquals,
BindingPath,
Common,
Press,
EnterText) {
"use strict";
var sViewName = "Worklist",
sTableId = "table";
Opa5.createPageObjects({
onTheWorklistPage: {
baseClass: Common,
actions: {
...
},
iSearchFor: function (sSearchString) {
return this.waitFor({
id: "searchField",
viewName: sViewName,
actions: new EnterText({
text: sSearchString
}),
errorMessage: "SearchField was not found."
});
}
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
391
},
assertions: {
theTableHasOneItem: function () {
return this.waitFor({
id: sTableId,
viewName: sViewName,
matchers: new AggregationLengthEquals({
name: "items",
length: 1
}),
success: function () {
Opa5.assert.ok(true, "The table contains one
corresponding entry");
},
errorMessage: "The table does not contain one item."
});
},
...
For the new test case we add an action iEnterSearchStringIntoSearchField and a new assertion
theTableShouldHaveCorrespondingEntries.
In iEnterSearchStringIntoSearchField, we use the EnterText action and load the dependency sap/ui/
test/actions/EnterText.
We define a waitFor statement with the current view and with the ID of our SearchField, which is stored as an
internal variable. This is done in the same way as in the iPressOnMoreData action that we implemented in our
first OPA test. But now we don't use the EnterText action. As soon as the SearchField is visible on the screen
and can be interacted with, the EnterText action is invoked. If is is not invoked, an error message is displayed and
the test fails.
The assert part is implemented in the same way as in our first OPA test. Again, we use the matchers to check the
state. Here we check the number of items in the table resulting from the simulated search. According to our mock
data, there should be only one item visible.
Conventions
Actions in OPA never contain a QUnit assertion.
Related Information
API Reference: sap.ui.test.actions.EnterText
392
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Step 12: Adding a Search
We now add a search field to our bulletin board and define a filter that represents the search term. This is done
similarly as in step 24 of the Walkthrough tutorial.
Preview
Figure 112: Search field
Coding
You can view and download all files in the Samples in the Demo Kit at Testing - Step 12.
webapp/view/Worklist.view.xml
...
<Table
id="table"
width="auto"
class="sapUiResponsiveMargin"
growing="true"
items="{
path: '/Posts',
sorter: {
path: 'Title',
descending: false
}
}"
busyIndicatorDelay="{worklistView>/tableBusyDelay}"
updateFinished="onUpdateFinished">
<headerToolbar>
<Toolbar>
<Label id="tableHeader" text="{worklistView>/
worklistTableTitle}"/>
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
393
<ToolbarSpacer />
<SearchField id="searchField" width="auto"
search="onFilterPosts" />
</Toolbar>
</headerToolbar>
...
We add a ToolbarSpacer and a SearchField to the headerToolbar of our table.
webapp/controller/Worklist.controller.js
sap.ui.define([
'sap/ui/demo/bulletinboard/controller/BaseController',
'sap/ui/model/json/JSONModel',
'sap/ui/demo/bulletinboard/model/formatter',
'sap/ui/demo/bulletinboard/model/FlaggedType',
"sap/ui/model/Filter",
"sap/ui/model/FilterOperator"
], function (BaseController, JSONModel, formatter, FlaggedType, Filter,
FilterOperator) {
"use strict";
...
onUpdateFinished: function (oEvent) {
// update the worklist's object counter after the table update
var sTitle,
oTable = oEvent.getSource(),
iTotalItems = oEvent.getParameter("total");
// only update the counter if the length is final and
// the table is not empty
if (iTotalItems && oTable.getBinding("items").isLengthFinal()) {
sTitle =
this.getResourceBundle().getText("worklistTableTitleCount", [iTotalItems]);
} else {
sTitle = this.getResourceBundle().getText("worklistTableTitle");
}
this.getModel("worklistView").setProperty("/worklistTableTitle",
sTitle);
},
onFilterPosts: function (oEvent) {
// build filter array
var aFilter = [];
var sQuery = oEvent.getParameter("query");
if (sQuery) {
aFilter.push(new Filter("Title", FilterOperator.Contains, sQuery));
}
...
},
// filter binding
var oTable = this.byId("table");
var oBinding = oTable.getBinding("items");
oBinding.filter(aFilter);
To enable filtering, we extend the controller with a method that applies the search term entered in the search field
to the list binding, similarly as we did for InvoiceList.controller.js in step 24 of the Walkthrough tutorial.
394
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Related Information
Step 42 of Walkthrough: Filtering [page 146]
Step 13: Testing User Interaction
In this step we want to write a test that simulates user interaction with an icon tab bar. We want to change the tab
and check if the correct content is shown.
Preview
Figure 113: Test interacting with an icon tab bar
Coding
You can view and download all files in the Samples in the Demo Kit at Testing - Step 13.
test/integration/journeys/PostJourney.js
sap.ui.require(
["sap/ui/test/opaQunit"],
function (opaTest) {
"use strict";
…
opaTest(…) {
// Actions
When.onTheBrowser.iPressOnTheForwardButton();
// Assertions
Then.onThePostPage.theTitleShouldDisplayTheName("Jeans");
});
opaTest("Should select the statistics tab", function (Given, When, Then) {
// Actions
When.onThePostPage.iPressOnTheTabWithTheKey("statistics");
// Assertions
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
395
});
Then.onThePostPage.iShouldSeeTheViewCounter()
.and.iTeardownMyAppFrame();
We extend the PostJourney.js file with a new test. It is important to move the Teardown to the last test,
otherwise our app would be removed and the test would not be able to find the Statistics tab.
Delete .and.iTeardownMyAppFrame(); from the last test in the file and add the new test case
test/integration/pages/Post.js
sap.ui.require([
'sap/ui/test/Opa5',
'sap/ui/test/matchers/Properties',
'sap/ui/demo/bulletinboard/test/integration/pages/Common',
'sap/ui/test/actions/Press'
],
function (Opa5, Properties, Common, Press) {
"use strict";
var sViewName = "Post";
page"
page");
Opa5.createPageObjects({
onThePostPage: {
baseClass: Common,
actions: {
iPressTheBackButton: function () {
return this.waitFor({
id: "page",
viewName: sViewName,
actions: new Press(),
errorMessage: "Did not find the nav button on object
});
},
iPressOnTheTabWithTheKey: function (sKey) {
return this.waitFor({
controlType: "sap.m.IconTabFilter",
viewName : sViewName,
matchers: new Properties({
key: sKey
}),
actions: new Press(),
errorMessage: "Cannot find the icon tab bar"
});
}
},
assertions: {
theTitleShouldDisplayTheName: function (sName) {
return this.waitFor({
id: "objectHeader",
viewName: sViewName,
matchers: new Properties({
title: sName
}),
success: function (oPage) {
Opa5.assert.ok(true, "was on the remembered detail
});
396
},
errorMessage: "The Post " + sName + " is not shown"
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
},
iShouldSeeTheViewCounter: function () {
return this.waitFor({
id: "viewCounter",
viewName: sViewName,
success: function () {
Opa5.assert.ok(true, "The view counter was
visible");
});
});
}
}
}
});
},
errorMessage: "The view counter could not be found"
To change the selected tab, you set the selected key of the sap.m.IconTabBar. We have looked up the
setSelectedKey function in the API documentation of the control, so we know that we can write a waitFor
statement that makes use of it.
There is no uniform way of triggering user interactions with OPA. In most cases it is sufficient to use the API of the
controls, e.g. setting a value. Note however, that calling the API methods of a control might not trigger the same
events as when pressing the control.
In the assert part we add a new assertion for checking the visibility of a control with the ID viewCounter. Each
waitFor statement checks if the control is rendered and visible unless you set visible: false. Therefore, we
only use the ok(true) assertion in the success handler of the waitFor statement since QUnit expects at least
one assertion for a test.
Conventions
● Actions in OPA never contain a QUnit assertion
Related Information
API Reference: sap.m.IconTabBar
API Overview and Samples: sap.m.IconTabBar
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
397
Step 14: Adding Tabs
We want to display statistics for posts, for example, how many times it was viewed. To achieve this, we implement
an icon tab bar with an Info tab and a Statistics tab. The existing content should be placed on the Info tab and the
view count on the Statistics tab.
Preview
Figure 114: An icon tab bar with statistics
Coding
You can view and download all files in the Samples in the Demo Kit at Testing - Step 14.
view/Post.view.xml
<mvc:View
controllerName="sap.ui.demo.bulletinboard.controller.Post"
xmlns="sap.m"
xmlns:form="sap.ui.layout.form"
xmlns:mvc="sap.ui.core.mvc"
xmlns:semantic="sap.m.semantic">
<semantic:FullscreenPage
id="page"
busy="{postView>/busy}"
busyIndicatorDelay="0"
navButtonPress="onNavBack"
showNavButton="true"
title="{i18n>objectTitle}">
<semantic:content>
<ObjectHeader
id="objectHeader"
title="{Title}"
number="{
path: 'Price',
formatter: '.formatter.numberUnit'
}"
398
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
numberUnit="{Currency}"
backgroundDesign="Translucent">
</ObjectHeader>
<IconTabBar id="iconTabBar"
expanded="{device>/isNoPhone}"
class="sapUiNoContentPadding">
<items>
<IconTabFilter icon="sap-icon://hint" key="info" >
<form:SimpleForm>
<form:content>
<Label text="{i18n>postDateLabel}"/>
<Text text="{Timestamp}"/>
<Label text="{i18n>postDescriptionLabel}"/>
<Text text="{Description}"/>
</form:content>
</form:SimpleForm>
</IconTabFilter>
<IconTabFilter icon="sap-icon://inspection" key="statistics">
<Text text="Viewed 55555 times" id="viewCounter"/>
</IconTabFilter>
</items>
</IconTabBar>
</semantic:content>
</semantic:FullscreenPage>
</mvc:View>
We add a sap.m.IconTabBar with the two tabs info and statistics. The statistics tab we have already
referred to in our test case.
Inside the first tab there is a sap.ui.layout.form.SimpleForm with a date and a description field that are
mapped to the model data. In the second tab we place a new Text control.
In this very simple example, we just put a static text in the tab. In a real application, we would bind the value to the
model.
webapp/i18n/i18n.properties
#~~~ Object View ~~~~~~~~~~~~~~~~~~~~~~~~~~
#XTIT: Object view title
objectTitle=Post
#XTIT: Post view date label
postDateLabel=Posted At
#XTIT: Post view description label
postDescriptionLabel=Description
#~~~ Footer Options ~~~~~~~~~~~~~~~~~~~~~~~
We add the missing texts to the i18n.properties file.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
399
Step 15: Writing a Short Date Formatter Using TDD
It's now time to improve the content of the Info tab. We want to see the Posted At date in a formatted way. Based
on the age of the post, we either display the time, a textural representation of the day, or the date only.
Preview
Figure 115: Unit tests of the formatter
Depending on the current date, we distinguish four different formatting categories, as shown in the table below:
Table 6: Formatting Categories
Category
Sample Input
Expected Output (for en-US)
Today
2013/02/13 12:05:20
12:05 PM
Yesterday
2013/02/12 12:05:20
Yesterday
Last 7 days
2013/02/08 12:05:20
Friday
Others
2011/02/05 12:05:20
Dec 5, 2011
As you can see, we have many different cases, and our formatter contains real logic.
We test this in a unit test. In this step we will follow an iterative approach. We first write a failing test and
immediately fix it by adding the production code to make the test pass. Then the next iteration starts. We do not
write more than one failing unit test at once.
Note
There are many benefits of consequently applying the test-driven development (TDD) methodology, for
example, very fast feedback, you can execute your tests after each change and get immediate feedback if the
tests run green. You also spend less time debugging and for analysis. We recommend that you get familiar with
TDD and clean code practices. In this step you get a first impression how TDD results in better separation of
concerns, APIs, handling of dependencies, code reuse, and a test suite growing together with the code.
400
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Coding
You can view and download all files in the Samples in the Demo Kit at Testing - Step 15.
webapp/test/unit/allTests.js
sap.ui.define([
"test/unit/model/models",
"test/unit/model/formatter",
"test/unit/model/FlaggedType",
"test/unit/model/DateFormatter"
], function() { "use strict"; });
First, we add the new test file we are about to create to the allTests.js file.
webapp/model/DateFormatter.js (New)
sap.ui.define([
"sap/ui/base/Object"
], function(Object) {
return Object.extend("sap.ui.demo.bulletinboard.model.DateFormatter", {
});
});
We create an empty hull for our formatter implementation first so that we can include it in our test. It does not
contain any logic yet but simply extends an OpenUI5 base object.
webapp/test/unit/model/DateFormatter.js (New)
sap.ui.require([ "sap/ui/demo/bulletinboard/model/DateFormatter" ],
function(DateFormatter) {
QUnit.module("DateFormatter");
QUnit.test("initial", function() {
ok(new DateFormatter());
});
});
And we create our test that checks if there is a DateFormatter object. Now we can execute our unit tests. We see
that this test is failing as the object does not exist in our code yet.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
401
webapp/test/unit/model/DateFormatter.js
sap.ui.require(["sap/ui/demo/bulletinboard/model/DateFormatter"],
function(DateFormatter) {
QUnit.module("DateFormatter");
QUnit.test("Should return empty string if no date is given", function(assert) {
var oFormatter = new DateFormatter();
var sFormattedDate = oFormatter.format(null);
assert.strictEqual(sFormattedDate, "");
});
});
Now we implement a test for the API of the format function. We assume it will have a Date object as input
parameter. In the first step, the test verifies that the format function returns an empty string if we pass null.
webapp/model/DateFormatter.js
sap.ui.define(["sap/ui/base/Object"],
function(Object) {
return Object.extend("sap.ui.demo.bulletinboard.model.DateFormatter", {
format: function() {
return "";
}
});
}
);
Now we fix our test again by returning the expected string.
Dependency Injection:
webapp/test/unit/model/DateFormatter.js
sap.ui.require([ "sap/ui/demo/bulletinboard/model/DateFormatter", "sap/ui/core/
Locale" ], function(DateFormatter, Locale) {
QUnit.module("DateFormatter");
QUnit.test("Should return empty string if no date is given", function(assert) {
var oFormatter = new DateFormatter({
locale : new Locale("en-US")
});
var sFormattedDate = oFormatter.format(null);
assert.strictEqual(sFormattedDate, "");
});
QUnit.test("Should return time if date from today", function(assert) {
var oFormatter = new DateFormatter({
locale : new Locale("en-US")
});
var oDate = new Date(2015, 2, 14, 12, 5, 0, 0);
var sFormattedDate = oFormatter.format(oDate);
assert.strictEqual(sFormattedDate, "12:05 PM");
});
});
402
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Here our test expects that the date is displayed as time when the post is from today. If we rely on the browser
language the test would be fragile. It will fail in some languages. To avoid this, we pass the locale settings to the
formatter’s constructor. The test will use a fixed locale en-US in order to remain stable. This mechanism is called
Dependency Injection.
webapp/model/DateFormatter.js
sap.ui.define(["sap/ui/base/Object", "sap/ui/core/format/DateFormat"],
function(Object, DateFormat) {
return Object.extend("sap.ui.demo.bulletinboard.model.DateFormatter", {
constructor: function(oProperties) {
this.timeFormat = DateFormat.getTimeInstance({
style: "short"
}, oProperties.locale);
},
format: function(oDate) {
if (!oDate) {
return "";
}
return this.timeFormat.format(oDate);
}
});
}
);
In the implementation we use the DateFormat of OpenUI5 to create a short date. The locale is passed on to the
getTimeInstance function.
Note
The implementation should not do more than the current tests covers. This makes sure you cover all the code
paths. You can enable the code coverage by selecting the Enable coverage checkbox.
It will show the lines covered by your tests (white) and the ones that were not covered (red). For the single test
above the coverage looks like this. The red line is already covered by the previous test so in total we have a test
coverage of 100%.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
403
Refactoring:
webapp/test/unit/model/DateFormatter.js
sap.ui.require(["sap/ui/demo/bulletinboard/model/DateFormatter" ],
function(DateFormatter) {
var oFormatter = null;
QUnit.module("DateFormatter", {
beforeEach: function() {
oFormatter = new DateFormatter({
locale: new Locale("en-US")
});
}
});
QUnit.test("Should return empty string if no date is given", function(assert) {
/*Delete in your code: var oFormatter = new DateFormatter();
...
});
QUnit.test("Should return time if date from today", function(assert) {
/*Delete in your code: var oFormatter = new DateFormatter({
/*Delete in your code:
locale: new Locale("en-US")
/*Delete in your code: });
...
});
});
Our tests are running so we can start refactoring our code. Since we need the DateFormatter object in every test
case we will move it to the QUnit module’s beforeEach function. As the name suggests, the function is invoked
before each test so we may use it to save some code we need in every test.
Dependency Injection to Get Independent from System Time:
webapp/test/unit/model/DateFormatter.js
sap.ui.require([ "sap/ui/demo/bulletinboard/model/DateFormatter", "sap/ui/core/
Locale"], function(DateFormatter, Locale) {
var oFormatter = null;
QUnit.module("DateFormatter", {
beforeEach : function() {
oFormatter = new DateFormatter({
now : function() {
return new Date(2015, 2, 14, 14, 0, 0, 0).getTime();
},
locale : new Locale("en-US")
});
}
});
...
QUnit.test("Should return 'Yesterday' if date from yesterday", function(assert)
{
var oDate = new Date(2015, 2, 13);
var sFormattedDate = oFormatter.format(oDate);
assert.strictEqual(sFormattedDate, "Yesterday");
});
});
404
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
The next test verifies that Yesterday is returned for yesterday's date. To keep the test independent of the system
time, we pass on a stable date to the formatter.
webapp/model/DateFormatter.js
sap.ui.define([ "sap/ui/base/Object", "sap/ui/core/format/DateFormat" ],
function(Object, DateFormat) {
return Object.extend("sap.ui.demo.bulletinboard.model.DateFormatter", {
constructor : function(oProperties) {
this.timeFormat = DateFormat.getTimeInstance({
style : "short"
}, oProperties.locale);
this.now = oProperties.now;
},
format : function(oDate) {
if (!oDate) {
return "";
}
var iElapsedDays = this._getElapsedDays(oDate);
if (iElapsedDays === 0) {
return this.timeFormat.format(oDate);
} else if (iElapsedDays === 1) {
return "Yesterday";
}
return this.dateFormat.format(oDate);
},
_getElapsedDays : function(oDate) {
var iElapsedMilliseconds = this.now() - oDate.getTime();
var fElapsedDays = iElapsedMilliseconds / 1000 / 60 / 60 / 24;
return Math.floor(fElapsedDays);
}
});
}
);
In the implementation we add a calculation for determining how many days passed. If zero days passed, the
format function is called, and if one day passed Yesterday is returned. Currently we skip reading "Yesterday"
from the i18n model to keep the example simple.
Boundary Testing:
webapp/test/unit/model/DateFormatter.js
sap.ui.require([ "sap/ui/demo/bulletinboard/model/DateFormatter", "sap/ui/core/
Locale" ], function(DateFormatter, Locale) {
var oFormatter = null;
...
QUnit.test("Should return weekday if date < 7 days ago", function(assert) {
var oDate = new Date(2015, 2, 8);
var sFormattedDate = oFormatter.format(oDate);
assert.strictEqual(sFormattedDate, "Sunday");
});
});
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
405
The next test verifies that the weekday is returned. As test input we take a value at the boundary: Sunday is one
day before a different formatting pattern should be applied.
webapp/model/DateFormatter.js
sap.ui.define([ "sap/ui/base/Object", "sap/ui/core/format/DateFormat" ],
function(Object, DateFormat) {
var DateFormatter =
…
return Object.extend("sap.ui.demo.bulletinboard.model.DateFormatter", {
constructor: function(oProperties) {
this.timeFormat = DateFormat.getTimeInstance({
style: "short"
}, oProperties.locale);
this.weekdayFormat = DateFormat.getDateInstance({
pattern: "EEEE"
}, oProperties.locale);
this.now = oProperties.now;
},
format: function(oDate) {
if (!oDate) {
return "";
}
var iElapsedDays = this._getElapsedDays(oDate);
if (iElapsedDays === 0) {
return this.timeFormat.format(oDate);
} else if (iElapsedDays === 1) {
return "Yesterday";
} else if (iElapsedDays < 7) {
return this.weekdayFormat.format(oDate);
}
}…
Now we define a new format in our constructor, the weekdayFormat. In the format function we apply the format if
the elapsed days are smaller than 7.
webapp/test/unit/model/DateFormatter.js
sap.ui.require(["sap/ui/demo/bulletinboard/model/DateFormatter"],
function(DateFormatter) {
var oFormatter = null;
...
QUnit.test("Should return date w/o time if date > 7 days ago", function(assert)
{
var oDate = new Date(2015, 2, 7);
var sFormattedDate = oFormatter.format(oDate);
assert.strictEqual(sFormattedDate, "Mar 7, 2015");
});
});
In the next test we verify that the date is formatted as date without time. Again, we take a value at the boundary.
406
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/model/DateFormatter.js
…
…
constructor: function(oProperties) {
this.timeFormat = DateFormat.getTimeInstance({
style : "short"
}, oProperties.locale);
this.weekdayFormat = DateFormat.getDateInstance({
pattern : "EEEE"
}, oProperties.locale);
this.dateFormat = DateFormat.getDateInstance({
style : "medium"
}, oProperties.locale);
this.now = oProperties.now;
},
format: function(oDate) {
if (!oDate) {
return "";
}
var iElapsedDays = this._getElapsedDays(oDate);
if (iElapsedDays === 0) {
return this.timeFormat.format(oDate);
} else if (iElapsedDays === 1) {
return "Yesterday";
} else if (iElapsedDays < 7) {
return this.weekdayFormat.format(oDate);
} else {
return this.dateFormat.format(oDate);
}
},
In the implementation, we use a different style property for instantiating the dateFormat property. We call the
format of this instance for dates that are more than 6 days in the past.
Although our formatter depends on system time and locale settings, our tests are very easy to read and maintain.
We wrote blackbox tests, providing only the input and expecting a certain output without knowing the
implementation details. The DateFormatter does not actively resolve the dependencies to the system time and
locale settings. Instead, it asks its creator to pass the dependencies along in the constructor. In the next step, we
have to bring the pieces together.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
407
Step 16: Adding the Date Formatter
Our formatter does its job, but it is not yet used. In this step we will use it.
Preview
Figure 116: Date formatter in action
Coding
You can view and download all files in the Samples in the Demo Kit at Testing - Step 16.
webapp/view/Post.view.xml
…
<IconTabBar id="iconTabBar"
expanded="{device>/isNoPhone}"
class="sapUiResponsiveContentPadding">
<items>
<IconTabFilter icon="sap-icon://hint" key="info">
<form:SimpleForm>
<form:content>
<Label text="{i18n>postDateLabel}"/>
<Text text="{
path: 'Timestamp',
formatter: '.formatter.date'
}"/>
<Label text="{i18n>postDescriptionLabel}"/>
<Text text="{Description}"/>
</form:content>
</form:SimpleForm>
</IconTabFilter>
…
</items>
</IconTabBar>
408
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
…
On the Info tab we bind the date field to a format method .formatter.date of the controller of the view. The
leading . indicates that the function is defined on the controller instance.
webapp/model/formatter.js
sap.ui.define([ "sap/ui/demo/bulletinboard/model/DateFormatter" ], function
(DateFormatter) {
...
return {
...
numberUnit: function(sValue) {
...
},
date: function(date) {
return new DateFormatter({now: Date.now}).format(date);
}
};
});
In the formatter.js file, create an instance of the previously implemented DateFormatter and provide the
necessary dependencies.
Now run the app again to see that the formatter is applied on the post date of the detail page.
Note
The files that create objects with dependencies should be kept simple. They do not have multiple code paths
caused by if-else statements or loops. To test these components, just a few simple integration tests, or merely
smoke tests, are sufficient. We already know that the DateFormatter does the job right for all the different
cases.
Summary
You should now be familiar with the major development paradigms and concepts of OpenUI5 and have created a
very simple first app. You are now ready to build a proper app based on what you've learned.
Mock Server
In this tutorial, we will explore some advanced features of the mock server.
If no OData service is available or you simply don’t want to depend on the OData backend connectivity for your
development and tests, the mock server can mimic the OData back-end calls. It is designed to simulate an OData
provider by intercepting the HTTP communication made to the server, and providing a fake output. All this is
transparent to the data binding and usage of OData model.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
409
In certain scenarios, using only the built-in OData simulation of the mock server is insufficient for completely
server-independent tests. For example, if your application is using an OData feature that is not supported by the
mock server, or if your application invokes a function import that depends on server specific implementation (and
thus is also not simulated generically). We will demonstrate how to use function callbacks in order to change
existing mock requests.
Additionally, we will demonstrate how to mock an additional request that is not simulated out of the box by the
OpenUI5 mock server.
Caution
The tutorial describes how to use some advanced features of the mock server, disregarding the legal aspects of
shipping mock data. Usually the mock data and mock server invocation is done in a test folder that is not
shipped to customers. Be very careful that you don't ship mock data!
Preview
Tip
You don't have to do all tutorial steps sequentially, you can also jump directly to any step you want. Just
download the code from the previous step, and start there.
You can view and download the files for all steps in the Demo Kit at Mock Server. Copy the code to your
workspace and make sure that the application runs by calling the webapp/index.html file. Depending on your
development environment you might have to adjust resource paths and configuration entries.
For more information check the following sections of the tutorials overview page (see Get Started: Setup and
Tutorials [page 38]):
● Downloading Code for a Tutorial Step [page 40]
● Adapting Code to Your Development Environment [page 40]
410
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Prerequisites
This tutorial assumes you have access to the SAP Web IDE either by having a trial account or a customer account.
For more information, see App Development Using SAP Web IDE [page 43].
You should also be familiar with the concepts explained in the Walkthrough [page 86] tutorial and with the OData
specification.
Related Information
Mock Server [page 891]
Step 1: Initial App Without Data
We start with a simple app scenario with a list of items bound to an OData service. Since the OData service is not
available yet on a real server, we will use the mock server to simulate both data and data calls.
For this very simple tutorial app we will use an OData service called NerdMeetups that lists meet-up groups
according to location, date, topic, etc. The app will display a simple list populated by a function import call to
display only upcoming meet-ups (meet-ups with an event date greater to the current date).
Additionally, a button will fetch the first three meet-ups (using a custom URL parameter called first). This
exercise simply shows an app with no data retrieved from the back end. This can happen when the back end is
down, or when the service is not implemented yet.
Usually you start the development of an app with local mock data first. This way you can continue implementing
the application logic without depending on the back end readiness and connectivity.
Preview
Figure 117: The initial app
Coding
To set up your project for this tutorial, download the files for Step 1 in the Demo Kit at Mock Server - Step 1. Copy
the code to your workspace and make sure that the application runs by calling the webapp/test/
mockServer.html file.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
411
Depending on your development environment you might have to adjust resource paths and configuration entries.
The project structure and the files coming with this tutorial are explained in detail in the Walkthrough [page 86]
tutorial.
You should have the same files as displayed in the following figure:
Figure 118: Folder structure with downloaded files
Step 2: Creating a Mock Server to Simulate Data
In this step, we use the mock server to add data to our app without dependency to any remote server or system.
Preview
Figure 119: The app now contains data
Coding
You can view and download all files in the Demo Kit at Mock Server - Step 2.
412
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/test/mockServer.html
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title>Mock Server Tutorial</title>
<script id="sap-ui-bootstrap"
src="../../resources/sap-ui-core.js"
data-sap-ui-libs="sap.m"
data-sap-ui-theme="sap_belize"
data-sap-ui-xx-bindingSyntax="complex"
data-sap-ui-resourceroots='{"sap.ui.demo.MockServer": "../"}'>
</script>
<link rel="stylesheet" href="../css/style.css">
<script>
sap.ui.getCore().attachInit(function() {
sap.ui.require([
"sap/ui/demo/MockServer/localService/mockserver",
"sap/m/Shell",
"sap/ui/core/ComponentContainer"
], function (mockserver, Shell, ComponentContainer) {
mockserver.init();
new Shell({
app: new sap.ui.core.ComponentContainer({
height : "100%",
name : "sap.ui.demo.MockServer",
settings : {
id : "MockServer"
}
})
}).placeAt("content");
});
});
</script>
</head>
<body class="sapUiBody" id="content">
</body>
</html>
We use this file to run our app in test mode with mock data. We use the sap.ui.require syntax, because we load
more additional files required for the start of our app. The first dependency is a file called mockserver.js that is
located in the webapp/localService folder. The Shell and the ComponentContainer controls are also loaded
as a dependency by the require statement.
The new artifact that we just loaded and are about to implement is our local mock server that is immediately called
with init method before we actually instantiate the component. By doing so, we can catch all requests that would
go to the “real” service and process it locally by our mock server when launching the app with the webapp/test/
mockServer.html file.
Note
A productive application does not contain the mock server code and thus connects to the real service instead.
The HTML page above is defined only for local testing and to be called in automated tests. The application
coding itself is unchanged and does not know the difference between the real and the mocked back-end service.
The mock server does not need to be called from anywhere else in our code so we use sap.ui.require to
load dependencies asynchronously without defining a global namespace.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
413
webapp/localService/metadata.xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<edmx:Edmx Version="1.0"
xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">
<edmx:DataServices
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
m:DataServiceVersion="1.0">
<Schema Namespace="NerdMeetup.Models"
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns="http://schemas.microsoft.com/ado/2006/04/edm">
<EntityType Name="Meetup">
<Key>
<PropertyRef Name="MeetupID" />
</Key>
<Property Name="MeetupID" Type="Edm.Int32" Nullable="false" />
<Property Name="Title" Type="Edm.String" Nullable="true" />
<Property Name="EventDate" Type="Edm.DateTime" Nullable="false" />
<Property Name="Description" Type="Edm.String" Nullable="true" />
<Property Name="HostedBy" Type="Edm.String" Nullable="true" />
<Property Name="ContactPhone" Type="Edm.String" Nullable="true" />
<Property Name="Address" Type="Edm.String" Nullable="true" />
<Property Name="Country" Type="Edm.String" Nullable="true" />
<Property Name="Latitude" Type="Edm.Double" Nullable="false" />
<Property Name="Longitude" Type="Edm.Double" Nullable="false" />
<Property Name="HostedById" Type="Edm.String" Nullable="true" />
<Property Name="Location" Type="NerdMeetup.Models.LocationDetail"
Nullable="false" />
</EntityType>
<ComplexType Name="LocationDetail" />
<EntityContainer Name="NerdMeetups" m:IsDefaultEntityContainer="true">
<EntitySet Name="Meetups" EntityType="NerdMeetup.Models.Meetup" />
<FunctionImport Name="FindUpcomingMeetups" EntitySet="Meetups"
ReturnType="Collection(NerdMeetup.Models.Meetup)" m:HttpMethod="GET" />
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
The metadata file contains information about the service interface and does not need to be written manually. It
defines a Meetup entity, a Meetups entity set and a function import definition.
webapp/localService/mockdata/Meetups.json (New)
[{
"MeetupID": 1,
"Title": "Toronto Tech Meet-Up",
"EventDate": "/Date(1278450000000)/",
"Description": "The best way to expand your knowledge and network of the
Toronto technology community"
}, {
"MeetupID": 2,
"Title": "Los Angeles Redditors",
"EventDate": "/Date(1478171994000)/",
"Description": "This is a meet-up group specifically for redditors of r/
LosAngeles. If you don't know what that is, this isn't the meet-up you're looking
for"
}, {
"MeetupID": 3,
414
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
"Title": "Designers + Geeks New York",
"EventDate": "/Date(1070968794000)/",
"Description": "Bringing designers + geeks together to talk shop, start-ups,
and do some knowledge sharing. All types of designers + geeks welcome"
}, {
"MeetupID": 4,
"Title": "New York City Geek Adventure Group",
"EventDate": "/Date(1034763594000)/",
"Description": "Are you looking to have fun and go on random adventures?"
}]
The Meetups.json file is automatically read by the mock server later in this step. It represents a flat array of
Meetup items.
webapp/localService/mockserver.js (New)
sap.ui.define([
"sap/ui/core/util/MockServer"
], function(MockServer) {
"use strict";
return {
/**
* Initializes the mock server.
* You can configure the delay with the URL parameter "serverDelay".
* The local mock data in this folder is returned instead of the real data
for testing.
* @public
*/
init: function() {
// create
var oMockServer = new MockServer({
rootUri: "/"
});
// simulate against the metadata and mock data
oMockServer.simulate("../localService/metadata.xml", {
sMockdataBaseUrl: "../localService/mockdata",
bGenerateMissingMockData: true
});
// start
oMockServer.start();
jQuery.sap.log.info("Running the app with mock data");
}
};
});
Now we can write the code to initialize the mock server that will simulate the requests instead of the real server. We
load the MockServer module as a dependency and create a helper object that defines an init method to start
the server. This method is called before the Component initialization in the mockServer.html file above. The
init method creates a MockServer instance with the same URL as the real service. The URL in configuration
parameter rootURI is now served by our test server instead of the real service.
Next, we set two global configuration settings for all MockServer instances that tell the server to respond
automatically and introduce a delay of one second to imitate a typical server response time.
In order to simulate a manual back-end call we can simply call the simulate method on the MockServer instance
with the path to our newly created metadata.xml file. This will read the test data from our local file system and
set up the URL patterns that will mimic the real service. The first parameter is the path to the service
metadata.xml document. The second parameter is an object with the following properties:
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
415
● sMockdataBaseUrl: path where to look for mock data files in JSON format
● bGenerateMissingMockData: Boolean property to tell the MockServer to use auto-generated mock data in
case no JSON files are found.
We call the function start on the mock server instance. From this point on, each request matching the URL
pattern rootURI will be processed by the MockServer.
Finally, we add a message toast to indicate for the user that the app runs with mock data.
This approach is perfect for local and automated testing, even without any network connection. Your development
does not depend on the availability of a remote server, i.e. to run your tests independently from the back-end
service. You can control the mocked data so the requests will return reliable and predictable results.
If the real service connection cannot be established, for example, when there is no network connection, you can
always fall back to the local test page and run the app with mock data.
Just run the app now again with the mockServer.html file.. The list should now be populated with meet-ups from
our mock data. You can also choose the button and see data.
Related Information
Mock Server [page 891]
API Reference: sap.ui.core.util.MockServer
Step 3: Handling Custom URL Parameters
In this step, we add the functionality to interpret URL parameters in our local mock server configuration.
We know that the OData provider of this service implements a URL parameter that returns only the first three
entries of a set. So, for example, calling the URL with parameter/Meetups?first=3 should return only the first 3
meet-up entries instead of all available entries.
416
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Preview
Figure 120: Only the next three meet-ups are shown
Coding
You can view and download all files in the Demo Kit at Mock Server - Step 3.
webapp/localService/mockserver.js
sap.ui.define([
"sap/ui/core/util/MockServer"
], function(MockServer) {
"use strict";
return {
/**
* Initializes the mock server.
* You can configure the delay with the URL parameter "serverDelay".
* The local mock data in this folder is returned instead of the real data
for testing.
* @public
*/
init: function() {
// create
var oMockServer = new MockServer({
rootUri: "/"
});
oMockServer.simulate("../localService/metadata.xml", {
sMockdataBaseUrl: "../localService/mockdata",
bGenerateMissingMockData: true
});
// handling custom URL parameter step
var fnCustom = function(oEvent) {
var oXhr = oEvent.getParameter("oXhr");
if (oXhr && oXhr.url.indexOf("first") > -1) {
oEvent.getParameter("oFilteredData").results.splice(3, 100);
}
};
oMockServer.attachAfter("GET", fnCustom, "Meetups");
// start
oMockServer.start();
jQuery.sap.log.info("Running the app with mock data");
}
};
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
417
});
In some scenarios, a server-specific implementation is used to calculate the returned data. For example, you can
use a custom URL parameter that is typically interpreted by the server. The mock server ignores it, thus still
returning the entire set of meet-ups.
In this tutorial, we use the URL parameter first=3 to fetch the first three entries. So, for example, calling to /
Meetups?first=3 should return at most three meet-up entries.
However, since this is a custom parameter that is not part of the standard official OData query options, it will not
get processed correctly by the mock server. Moreover, the mock server simply ignores it and return the entire set
of meet-ups.
We now enable the functionality when running in mock mode. As its functionality corresponds to the OData $top
system query, we simply evaluate it to $top at runtime.
First, we create a callback function that we later attach to every GET request made to the Meetups entity set of the
service. Note that we choose the attachAfter event that is fired after the built-in request processing of the mock
server. The event contains the actual XHR object and the mock data to be returned to the application. Inside the
callback function we remove all results starting from third entry: The oFilteredData parameter comes with the
event attachAfter and contains the mock data entries that are about to be returned in the response.
Second, we attach the callback to every GET request to the specific Meetups entity set.
Step 4: Calling a Function Import
We only want to display the upcoming meetings and hide the meetings happened in the past in our app. By using a
function import that calculates these items on the back end we do not need to do the calculation on the client. The
mock server will be instructed to do the calculation locally for testing purposes.
Preview
Figure 121: Only the upcoming meet-ups are shown
Coding
You can view and download all files in the Demo Kit at Mock Server - Step 4.
418
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
webapp/localService/metadata.xml
...
<EntityContainer Name="NerdMeetups" m:IsDefaultEntityContainer="true">
<EntitySet Name="Meetups" EntityType="NerdMeetup.Models.Meetup" />
<FunctionImport Name="FindUpcomingMeetups" EntitySet="Meetups"
ReturnType="Collection(NerdMeetup.Models.Meetup)" m:HttpMethod="GET" />
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
The function import we are going to use is declared in the metadata.xml file.
webapp/view/App.view.xml
...
//Delete items="{/Meetups}"
<List headerText="Upcoming Meetups" id="list1" items="{/FindUpcomingMeetups}"
...
We change the binding of the list to a function import call that returns only upcoming meet-ups, instead of the call
to the entire meet-ups collection.
After saving and running the app again, we should get the following result:
Figure 122: No data visible
Since the function import call is not simulated automatically by the mock server, we do not see any data in list, and
a failed network call is issued in the developer tools of the browser.
Tip
In Google Chrome, mocked requests will appear in a debug level log of the console (both request and response)
and not on the Network tab. If you do see them in the Network tab, they are not mocked and you need to check
your code.
In order to simulate the function import call, we write our own (mocked) implementation, and add to the internal
list of requests.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
419
webapp/localService/mockserver.js
...
oMockServer.simulate("localService/metadata.xml", {
sMockdataBaseUrl : "localService/mockdata",
bGenerateMissingMockData : true
});
var aRequests = oMockServer.getRequests();
aRequests.push({
method: "GET",
path: new RegExp("FindUpcomingMeetups(.*)"),
response: function(oXhr, sUrlParams) {
jQuery.sap.log.debug("Incoming request for FindUpcomingMeetups");
var today = new Date();
today.setHours(0); // or today.toUTCString(0) due to timezone differences
today.setMinutes(0);
today.setSeconds(0);
var oResponse = jQuery.sap.sjax({
url: "/Meetups?$filter=EventDate ge " + "/Date(" + today.getTime() +
")/"
});
oXhr.respondJSON(200, {}, JSON.stringify(oResponse.data));
return true;
}
});
oMockServer.setRequests(aRequests);
var fnCustom = function(oEvent) {
...
We push a new request handler to mock the function import call as follows:
1. Fetch the array of requests from the MockServer. The mock server holds an internal list of requests that you
have to get and set if you want to modify.
2. Push a new request handler to handle the function import
3. Set the updated request array
The request handler has the following structure:
● method: The HTTP method of the mock request
● path: The relative path (appended to the rootUri) of the request.
We can define the path as a regular expression, for example, to handle URL parameters.
● response: A response function that simulates the answer from the server
The response function executes a request to the Meetups entity set with an OData $filter query that actually
returns all meet-ups with EventDate that is greater than or equals today. We compose a date for the filter and
send it to the server manually as a synchronous request.
It is o.k. to use jQuery.sap.sjax here, because the call will not actually leave the client. It triggers a new request
that again is intercepted and processed by the mock server.
We finally respond on the XHR object by calling the respondJSON API. It will add the proper content type header
for the JSON format and send the result. We provide the HTTP status code 200 (success) and the 'stringified'
response data as the arguments. Returning true at the end of the function indicates that we have completed the
processing of the request in this handler (no additional request handlers should be checked for that request).
When you now start the app again you will see a list of upcoming meet-ups.
420
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Creating and Editing Mock Data in SAP Web IDE (Optional)
webapp/localService/mockserver.js
...
oMockServer.simulate("localService/metadata.xml", {
sMockdataBaseUrl : "localService/mockdata",
bGenerateMissingMockData : true
});
...
The path we gave in the simulate function for mock data is where we want to store the .json file(s).
● Save it (in JSON format) from a real service
● Create it manually
● Generate it in SAP Web IDE by choosing Edit Mock Data in the context menu of the medatdata.xml file. For
more information about SAP Web IDE, see the documentation for SAP Web IDE on the SAP Help Portal at
https://help.sap.com/viewer/p/SAP_Web_IDE.
Figure 123: Editing mock data in SAP Web IDE
Worklist App
In this tutorial we will build an app using OpenUI5 that, for example, a shop owner can use to manage his product
stock levels.
The app provides the following features:
● Overview of all products
● Track products with shortages or products that are completely out of stock
● Reorder products that are low in stock
● View product details and add comments
We will use the worklist template as a starting point for this tutorial and add additional features to the app as we go
through the steps. The template implements a typical "Worklist" floorplan, one of the patterns that are specified by
the SAP Fiori design guidelines, but you can also use it as a starting point for easily creating any kind of list-based
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
421
apps. For more information about worklist floorplans, see the Related Information section at the bottom of this
topic.
Preview
Figure 124: Start page of the app with list of products and actions
422
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Figure 125: Product detail page of the app
Choose your development environment
You can do this tutorial either with SAP Web IDE or choose your own development environment:
● If you use SAP Web IDE, you don't need to set up a development environment, a server and so on. All you need
is a browser and an account for the SAP Cloud Platform. If you don't have an account yet, you can easily get a
trial account. We recommend to continue with the SAP Web IDE as it has out-of-the-box support for SAPUI5
and because there is no setup overhead at all.
In this case, you start with the template that is available in SAP Web IDE as described in option 1 for step 1 of
this tutorial (see Step 1 (Option 1): Creating the Initial App with an App Template in SAP Web IDE [page 424]).
● If you want to use your local development environment and deploy to any Web server of your choice, you
download the code for step 1 from the Demo Kit at Worklist App. In this case, start with option 2 for step 1 of
this tutorial (see Step 1 (Option 2): Downloading the Code [page 428]).
There might be some small differences between the worklist app code generated by the SAP Web IDE template
and the code downloaded from the Demo Kit. However, the differences are small and not relevant for the purpose
of this tutorial
Tip
You don't have to do all tutorial steps sequentially, you can also jump directly to any step you want. Just
download the code from the previous step, copy it to your workspace and make sure that the application runs
by calling the webapp/test/testService.html file.
For more information check the following sections of the tutorials overview page (see Get Started: Setup and
Tutorials [page 38]):
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
423
● Downloading Code for a Tutorial Step [page 40]
● Adapting Code to Your Development Environment [page 40]
Related Information
Worklist Template [page 1012]
Get a SAP Cloud Platform trial account
Step 1 (Option 1): Creating the Initial App with an App Template
in SAP Web IDE
This first step is only relevant if you decided to use the SAP Web IDE. In this step, we will set up the worklist app
using a template and configure the service to display products in the app. The template includes generic app
functionality and tests that can be easily extended with custom functionality for our use case.
Prerequisites
Set up your SAP Web IDE and define a destination to the Northwind OData service as described under App
Development Using SAP Web IDE [page 43].
424
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Preview
Figure 126: The worklist app
Create the Initial App Using the Template Wizard
1. Launch SAP Web IDE.
2. Choose
File
New
Project from Template
3. Select the SAP Fiori Worklist Application template and choose the latest SAPUI5 version in the Available
Version field. Choose Next.
4. On the Basic Information screen, enter MyWorklistApp as project name, don't specify an application domain.
5. On the Data Connection screen, select Service URL in the Sources area.
Under Service Information, choose Northwind OData Service and enter the URL /V2/Northwind/
Northwind.svc/ .
Note
If you cannot find the Northwind service, create the destination as described under Create a Northwind
Destination [page 47].
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
425
Validate the URL by choosing the Load service metadata button beneath the URL. In the Details area, you
should now see the service entities as displayed in the following screenshot:
Note
At runtime, the relative URL /V2/Northwind/Northwind.svc/ is prefixed with /destinations/
northwind. As a result, all our Northwind OData requests will be proxied via the Northwind OData Service
destination that is defined in the SAP Cloud Platform Cockpit. The destination contains the URL to the
resource http://services.odata.org and has the proxy type Internet. From this configuration the
proxy knows where the requests shall go to.
Instead of /V2/Northwind/Northwind.svc/, you could also enter the complete URL http://
services.odata.org/V2/Northwind/Northwind.svc/.
Choose Next.
6. On the Template Customization screen, enter the following data:
Table 7: Application Settings
Field
Value
Description
Title
Manage Products
Title of the app which will displayed as
header.
Namespace
mycompany.myapp
The application namespace is a unique
identifier for your application resour­
ces.
Description
My Worklist App
Short description of your app.
Type
Standalone App
We create a standalone app that can be
run without SAP Fiori launchpad (FLP).
If you choose to build an App for SAP
Fiori Launchpad, you automatically get
test HTML files with the FLP Sandbox,
and the app automatically includes ad­
ditional features like Save as Tile or
Share in SAP Jam.
426
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Table 8: Data Binding
Field
Value
Object Collection
Products
Description
This is the main entity set that will be
displayed in the app. Some of the other
fields below are automatically selected
depending on this field.
Object Collection ID
ProductID
The unique key that is used to identify
the object collection.
Object Title
ProductName
The display name of the main entity.
Object Numeric Attribute
UnitsInStock
The number displayed next to the prod­
uct name. In this scenario we pick the
UnitsInStock. This represents the
stock quantity of the product.
7. Choose Next and Finish
A new folder MyWorklistApp is now available in your local workspace. It contains the following files and
folders of the initial app:
Figure 127: Folder structure of the initial project
Note
The auto-generated files project.json and neo-app.json are only necessary for working with SAP Web
IDE. They are not included in case you download the example code from the Samples.
These three files will not be changed throughout this tutorial, so we will ignore them in the following steps.
8. Run the app.
Our component-based app does not have an index.html file. A component-based app would typically be
included in a surrounding container, like the SAP Fiori launchpad and therefore it does not need to have an
index.html file.
To be able to run the app in different test modes (for example, with or without mock server), there are several
HTML files available in the webapp/test folder.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
427
To run the app in test mode in SAP Web IDE, choose
Run
Run in FLP Sandbox (Mock Server) .
Note
The template comes with two run configurations for SAP Web IDE, you can either run the app with data
from a real back-end service (Run in FLP Sandbox) or with local mock data (Run in FLP Sandbox (Mock
Server)).
We choose the mock server option, because then the app will still be able to run even if the back end is
unavailable or the service is not implemented yet. We could even configure a delay to make local testing
more realistic.
You should see the screen, which contains generated mock data.
When you choose the Back button, you should see an SAP Fiori launchpad with a list of tiles each representing
an application. There you will also see the Manage Products tile. Choose that to run our generated app again.
From now on you can quickly run the app by selecting the root folder MyWorklistApp of your project in SAP Web
IDE and pressing the Run button. The system will automatically use the option from the Run menu that you chose
last (in this case, the Run with MockServer option).
Note
The texts in the i18n.properties file are automatically generated based on the template Customizing (OData
entity set, entities, properties, and texts). The result can be incorrect texts like "Enter an <Products> name or a
part of it." You should therefore revise the generated texts in the i18n.properties file.
Related Information
App Development Using SAP Web IDE [page 43]
Step 1 (Option 2): Downloading the Code
In this step, we set up the initial app without SAP Web IDE.
If you are working with another IDE or development environment than the SAP Web IDE, you can simply download
the code from the Samples in the Demo Kit at Worklist App - Step 1 and skip the wizard steps described in the
previous step of this tutorial. The code contains a preconfigured application project that can be used as a starting
point to develop the worklist app. You can deploy the downloaded application to a (local) Web server and call the
webapp/test/testService.html file in your browser manually to start the app.
To access the real service, you would need to set up a proxy service that connects your app project deployed on a
Web server to the remote service. Due to the so called same-origin policy browsers deny AJAX requests to service
endpoints in case the domain/subdomain, protocol, or port differ from the app’s domain/subdomain, protocol, or
port. Cross-origin resource sharing (CORS) allows to break out of these restrictions derived from the same-origin
policy. With CORS the server and browser agree which cross-origin requests are allowed. Another way to bypass
the same-origin policy is using a proxy on the same host of the app. To keep it simple, our app contains a test page
to run the app with local mock data instead of retrieving the data from a real server hosted somewhere else. This
428
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
way we won’t have any issues related to the same-origin policy of the browsers, as long as we run the app with our
mock server.
Note
The texts in the i18n.properties file are automatically generated based on the template Customizing (OData
entity set, entities, properties, and texts). The result can be incorrect texts like "Enter an <Products> name or a
part of it." You should therefore revise the generated texts in the i18n.properties file.
Related Information
Development Environment [page 41]
Step 1 (Result): The Initial App
With the generated code (SAP Web IDE) or the downloaded code from the Demo Kit, you have an initial app
structure with the following content inside the webapp folder.
● Home Page (webapp/view/Worklist.view.xml file)
The home page of the app shows a table of products including the corresponding number of units in the stock.
The title of the table shows how many items are available. A search field in the header toolbar of the table
allows to search for a product by name. Pressing a table row navigates the user to a new page that shows the
details of the pressed product.
● Data
You can run the app with the real service or with the mock server serving mock data. In the webapp/
localService/mockserver.js file, the mock server is configured. Using the mock server in this tutorial
allows us to easily run the code even without network connection and without the need of having a remote
server for our application data. To run the app with the mock server and its corresponding mock data the /
webapp/test/testService.html file has to be called in the browser.
The webapp/localService/metadata.xml file is used by the mock server to describe our OData service.
In this step, the mock server will generate mock data based on this file. In a subsequent step the mock server
will use our own custom mock data.
● Configuration of the App
In the webapp/manifest.json descriptor file, we configure our app. The descriptor file contains the
following relevant sections:
○ sap.app
In this section we reference an i18n.properties file and use a special syntax to bind the texts for the
title and description properties. In the dataSources section, we tell our app where to find our
mainService OData service. As you might guess, the URI correlates to the rootUri of our mock server
instance which can be found in webapp/localService/mockserver.js. It is important that these two
paths match to allow our mock server to provide the test data we defined above.
○ sap.ui5
In the sap.ui5 section, we declare with the rootView parameter that our
myCompany.myApp.view.App view shall be loaded and used as the rootView for our app.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
429
Furthermore, we define two models to be automatically instantiated and bound to the component: an
i18n model and a default model "". The latter references our mainService dataSource which is
declared in our sap.app section as an OData 2.0 data source. The i18n file can be found at webapp/
i18n/i18n.properties. The mainService data source will be mocked by our mock server.
Note
There is no webapp/index.html file in this tutorial. Instead, there is a test.html file in the webapp
folder. This test.html file serves as an easy entry point for developers to run and test the app in various
ways during development. It contains links to the relevant files inside our app to run the app in different
modes, i.e. with the MockServer or with data from the real Northwind service (only in case you generated
the code from the SAP Web IDE template).
Related Information
App Templates: Kick Start Your App Development [page 1011]
Folder Structure: Where to Put Your Files [page 1041]
Step 2: Custom Mock Data
In this step, we want to change the mock data of the initial app. The service metadata only contains a description
of the service entities. The mock server that is part of the app will auto-generate random mock data based on the
data types defined in the metadata file. To have a more realistic development environment we will now add
additional sample data.
430
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Preview
Figure 128: The product list of the initial app with custom mock data
Figure 129: Folder structure for this step including custom mock data
The webapp/localService/metadata.xml file used by the mock server describes our OData service. The
service only has two OData entities, and the data for these two entities is located in the folder webapp/
localService/mockdata:
● Products
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
431
A product has typical properties like ProductName and UnitsInStock as well as a navigation property to a
supplier entity referenced by a SupplierID. Of course, the entity has an ID property ProductID. The
corresponding EntitySet is Products. The actual test data containing several products is located in the
webapp/localService/mockdata/Products.json file.
● Suppliers
Later in this tutorial, we will display some information about the supplier of a product. The properties are
CompanyName, Address, City, PostalCode, Country, and so on; all of them contain textual information of
type Edm.String. The entity has an ID property SupplierID and the corresponding EntitySet is
Suppliers. The supplier data for products is located in the file webapp/localService/mockdata/
Suppliers.json.
Coding
You can view and download all files in the Samples in the Demo Kit at Worklist App - Step 2.
webapp/localService/mockdata/Products.json (New)
[
{
"ProductID": 1,
"ProductName": "Chai",
"UnitsInStock": 39,
"UnitsOnOrder": 10,
"UnitPrice": 8,
"SupplierID": 1,
"Discontinued": false,
"Supplier": {
"__deferred": {
"uri": "/destinations/northwind/V2/Northwind/Northwind.svc/Products(1)/
Supplier"
}
}
},
{
"ProductID": 2,
"ProductName": "Chang",
"UnitsInStock": 0,
"UnitsOnOrder": 7,
"UnitPrice": 6,
"SupplierID": 1,
"Discontinued": true,
"Supplier": {
"__deferred": {
"uri": "/destinations/northwind/V2/Northwind/Northwind.svc/Products(2)/
Supplier"
}
}
},
{
"ProductID": 3,
"ProductName": "Aniseed Syrup",
"UnitsInStock": 100,
"UnitsOnOrder": 6,
"UnitPrice": 3,
"SupplierID": 3,
"Discontinued": false,
"Supplier": {
"__deferred": {
"uri": "/destinations/northwind/V2/Northwind/Northwind.svc/Products(3)/
432
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Supplier"
}
}
},
{
"ProductID": 4,
"ProductName": "Schwarzwälder Kirschtorte",
"UnitsInStock": 2,
"UnitsOnOrder": 3,
"UnitPrice": 19,
"SupplierID": 3,
"Discontinued": false,
"Supplier": {
"__deferred": {
"uri": "/destinations/northwind/V2/Northwind/Northwind.svc/Products(4)/
Supplier"
}
}
},
{
"ProductID": 5,
"ProductName": "Chef Anton's Cajun Seasoning",
"UnitsInStock": 0,
"UnitsOnOrder": 9,
"UnitPrice": 108,
"SupplierID": 3,
"Discontinued": false,
"Supplier": {
"__deferred": {
"uri": "/destinations/northwind/V2/Northwind/Northwind.svc/Products(5)/
Supplier"
}
}
},
{
"ProductID": 6,
"ProductName": "Chef Anton's Gumbo Mix",
"UnitsInStock": 21,
"UnitsOnOrder": 0,
"UnitPrice": 18,
"SupplierID": 4,
"Discontinued": false,
"Supplier": {
"__deferred": {
"uri": "/destinations/northwind/V2/Northwind/Northwind.svc/Products(6)/
Supplier"
}
}
},
{
"ProductID": 7,
"ProductName": "Grandma's Boysenberry Spread",
"UnitsInStock": 25,
"UnitsOnOrder": 25,
"UnitPrice": 18,
"SupplierID": 5,
"Discontinued": false,
"Supplier": {
"__deferred": {
"uri": "/destinations/northwind/V2/Northwind/Northwind.svc/Products(7)/
Supplier"
}
}
},
{
"ProductID": 8,
"ProductName": "Uncle Bob's Organic Dried Pears",
"UnitsInStock": 29,
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
433
"UnitsOnOrder": 7,
"UnitPrice": 35,
"SupplierID": 6,
"Discontinued": false,
"Supplier": {
"__deferred": {
"uri": "/destinations/northwind/V2/Northwind/Northwind.svc/Products(8)/
Supplier"
}
}
},
{
"ProductID": 9,
"ProductName": "Northwoods Cranberry Sauce",
"UnitsInStock": 4,
"UnitsOnOrder": 32,
"UnitPrice": 35,
"SupplierID": 6,
"Discontinued": false,
"Supplier": {
"__deferred": {
"uri": "/destinations/northwind/V2/Northwind/Northwind.svc/Products(9)/
Supplier"
}
}
},
{
"ProductID": 10,
"ProductName": "Mishi Kobe Niku",
"UnitsInStock": 40,
"UnitsOnOrder": 0,
"UnitPrice": 130,
"SupplierID": 5,
"Discontinued": false,
"Supplier": {
"__deferred": {
"uri": "/destinations/northwind/V2/Northwind/Northwind.svc/Products(10)/
Supplier"
}
}
},
{
"ProductID": 11,
"ProductName": "Ikura",
"UnitsInStock": 4,
"UnitsOnOrder": 10,
"UnitPrice": 13,
"SupplierID": 4,
"Discontinued": false,
"Supplier": {
"__deferred": {
"uri": "/destinations/northwind/V2/Northwind/Northwind.svc/Products(11)/
Supplier"
}
}
},
{
"ProductID": 13,
"ProductName": "Carnarvon Tigers",
"UnitsInStock": 36,
"UnitsOnOrder": 40,
"UnitPrice": 56,
"SupplierID": 3,
"Discontinued": false,
"Supplier": {
"__deferred": {
"uri": "/destinations/northwind/V2/Northwind/Northwind.svc/Products(13)/
Supplier"
434
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
}
}
},
{
"ProductID": 14,
"ProductName": "Teatime Chocolate Biscuits",
"UnitsInStock": 0,
"UnitsOnOrder": 40,
"UnitPrice": 7,
"SupplierID": 2,
"Discontinued": false,
"Supplier": {
"__deferred": {
"uri": "/destinations/northwind/V2/Northwind/Northwind.svc/Products(14)/
Supplier"
}
}
},
{
"ProductID": 15,
"ProductName": "Alice Mutton",
"UnitsInStock": 90,
"UnitsOnOrder": 20,
"UnitPrice": 75,
"SupplierID": 2,
"Discontinued": true,
"Supplier": {
"__deferred": {
"uri": "/destinations/northwind/V2/Northwind/Northwind.svc/Products(15)/
Supplier"
}
}
}
]
First create a new mockdata folder inside webapp/localService. Create a Products.json file, and copy and
paste the code.
webapp/localService/mockdata/Suppliers.json (New)
[
{
"SupplierID": 1,
"CompanyName": "New Orleans Cajun Delights",
"ContactName": "Shelley Burke",
"ContactTitle": "Order Administrator",
"Address": "P.O. Box 78934",
"City": "New Orleans",
"Region": "LA",
"PostalCode": "70117",
"Country": "USA"
},
{
"SupplierID": 2,
"CompanyName": "Exotic Liquids",
"ContactName": "Charlotte Cooper",
"ContactTitle": "Purchasing Manager",
"Address": "49 Gilbert St.",
"City": "London",
"Region": "UK",
"PostalCode": "EC1 4SD",
"Country": "UK"
},
{
"SupplierID": 3,
"CompanyName": "Grandma Kelly's Homestead",
"ContactName": "Regina Murphy",
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
435
]
"ContactTitle": "Sales Representative",
"Address": "707 Oxford Rd.",
"City": "Ann Arbor",
"Region": "MI",
"PostalCode": "48104",
"Country": "USA"
},
{
"SupplierID": 4,
"CompanyName": "Forêts d'érables",
"ContactName": "Chantal Goulet",
"ContactTitle": "Accounting Manager",
"Address": "148 rue Chasseur",
"City": "Ste-Hyacinthe",
"Region": "Québec",
"PostalCode": "J2S 7S8",
"Country": "Canada"
},
{
"SupplierID": 5,
"CompanyName": "Plutzer Lebensmittelgroßmärkte AG",
"ContactName": "Martin Bein",
"ContactTitle": "International Marketing Mgr.",
"Address": "Bogenallee 51",
"City": "Frankfurt",
"Region": "DE",
"PostalCode": "60439",
"Country": "Germany"
},
{
"SupplierID": 6,
"CompanyName": "Lyngbysild",
"ContactName": "Niels Petersen",
"ContactTitle": "Sales Manager",
"Address": "Lyngbysild Fiskebakken 10",
"City": "Lyngby",
"Region": "NL",
"PostalCode": "2800",
"Country": "Denmark"
},
{
"SupplierID": 7,
"CompanyName": "Formaggi Fortini s.r.l.",
"ContactName": "Elio Rossi",
"ContactTitle": "Sales Representative",
"Address": "Viale Dante, 75",
"City": "Ravenna",
"Region": "IL",
"PostalCode": "48100",
"Country": "Italy"
}
Create a Suppliers.json file, and copy and paste the code.
You can now run the app again and see the mock data in your app.
Note
In order to get realistic mock data you can call a real OData service directly in your browser to receive the real
data of a given Entity or EntitySet. Make sure to call the service with the system option $format=json, i.e.
http://services.odata.org/V2/Northwind/Northwind.svc/Products?$format=json. This will
return the data in JSON format, which is the format required for our mock data. This data is put into a local file
in your application’s webapp/localService/mockdata folder. The file name is expected to be the name of
the corresponding EntitySet ends with .json, for example Products.json. The obtained data from the
436
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
OData service can serve as a first set of mock data, which you can change to your needs if necessary. SAP Web
IDE offers also a dedicated editor for mock data that makes the maintenance of the data even easier.
Step 3: Extending the Worklist Table
In this step, we will edit the worklist table to include additional columns for our manage product stocks scenario.
We display the supplier, the product price, and the number of units on order for each product and format the
values accordingly.
Preview
Figure 130: The improved worklist table with new columns and formatting
Coding
You can view and download all files in the Demo Kit at Worklist App - Step 3.
webapp/view/Worklist.view.xml
…
<Table
id="table"
busyIndicatorDelay="{worklistView>/tableBusyDelay}"
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
437
…
class="sapUiResponsiveMargin"
growing="true"
growingScrollToLoad="true"
noDataText="{worklistView>/tableNoDataText}"
updateFinished="onUpdateFinished"
width="auto"
items="{
path: '/Products',
sorter: {
path: 'ProductName',
descending: false
},
parameters: {
'expand': 'Supplier'
}
}">
We want to display the supplier’s company name in a separate column in the table for each product. Therefore, we
extend the items aggregation of the table with an expand parameter for the Supplier entity. With this, the
supplier data will be already included in the service request for the products.
We expand the supplier because we want to avoid sending one additional request for each product to get the
supplier. Furthermore, this allows us to bind directly to {Supplier/CompanyName} later.
Note
OData’s “expand” Mechanism:
OData $expand is very helpful when combining data from different service entities. Instead of having to send an
additional service request for the second entity, we simply expand the service call to include the second entity
as well – similar to a join in a relational database. Have a look at the local service metadata definition file
webapp/localService/metadata.xml that represents the interface of our service. In the metadata you can
see a list of entities that are available in this service, for example Products and Suppliers. Each entity lists a
number of fields that we can bind to the properties of our view.
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/
edmx">
<edmx:DataServices xmlns:m="http://schemas.microsoft.com/ado/2007/08/
dataservices/metadata" m:DataServiceVersion="1.0">
<Schema Namespace="NorthwindModel" xmlns:m="http://
schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://
schemas.microsoft.com/ado/2008/09/edm">
<EntityType Name="Product">
<Key>
<PropertyRef Name="ProductID" />
</Key>
<Property Name="ProductID" Type="Edm.Int32" Nullable="false"
p8:StoreGeneratedPattern="Identity" xmlns:p8="http://schemas.microsoft.com/ado/
2009/02/edm/annotation" />
<Property Name="ProductName" Type="Edm.String" Nullable="false"
MaxLength="40" Unicode="true" FixedLength="false" />
<Property Name="SupplierID" Type="Edm.Int32" Nullable="true" />
<Property Name="UnitPrice" Type="Edm.Decimal" Nullable="true"
Precision="19" Scale="4" />
<Property Name="UnitsInStock" Type="Edm.Int16" Nullable="true" />
<Property Name="UnitsOnOrder" Type="Edm.Int16" Nullable="true" />
<Property Name="Discontinued" Type="Edm.Boolean"
Nullable="false" />
438
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
<NavigationProperty Name="Supplier"
Relationship="NorthwindModel.FK_Products_Suppliers" FromRole="Products"
ToRole="Suppliers" />
</EntityType>
<EntityType Name="Supplier">
<Key>
<PropertyRef Name="SupplierID" />
</Key>
<Property Name="SupplierID" Type="Edm.Int32" Nullable="false"
p8:StoreGeneratedPattern="Identity" xmlns:p8="http://schemas.microsoft.com/ado/
2009/02/edm/annotation" />
<Property Name="CompanyName" Type="Edm.String" Nullable="false"
MaxLength="40" Unicode="true" FixedLength="false" />
<Property Name="ContactName" Type="Edm.String" Nullable="true"
MaxLength="30" Unicode="true" FixedLength="false" />
<Property Name="ContactTitle" Type="Edm.String" Nullable="true"
MaxLength="30" Unicode="true" FixedLength="false" />
<Property Name="Address" Type="Edm.String" Nullable="true"
MaxLength="60" Unicode="true" FixedLength="false" />
<Property Name="City" Type="Edm.String" Nullable="true"
MaxLength="15" Unicode="true" FixedLength="false" />
<Property Name="Region" Type="Edm.String" Nullable="true"
MaxLength="15" Unicode="true" FixedLength="false" />
<Property Name="PostalCode" Type="Edm.String" Nullable="true"
MaxLength="10" Unicode="true" FixedLength="false" />
<Property Name="Country" Type="Edm.String" Nullable="true"
MaxLength="15" Unicode="true" FixedLength="false" />
<NavigationProperty Name="Products"
Relationship="NorthwindModel.FK_Products_Suppliers" FromRole="Suppliers"
ToRole="Products" />
</EntityType>
<Association Name="FK_Products_Suppliers">
<End Role="Suppliers" Type="NorthwindModel.Supplier"
Multiplicity="0..1" />
<End Role="Products" Type="NorthwindModel.Product"
Multiplicity="*" />
<ReferentialConstraint>
<Principal Role="Suppliers">
<PropertyRef Name="SupplierID" />
</Principal>
<Dependent Role="Products">
<PropertyRef Name="SupplierID" />
</Dependent>
</ReferentialConstraint>
</Association>
</Schema>
<Schema Namespace="ODataWeb.Northwind.Model" xmlns:m="http://
schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://
schemas.microsoft.com/ado/2008/09/edm">
<EntityContainer Name="NorthwindEntities"
p7:LazyLoadingEnabled="true" m:IsDefaultEntityContainer="true" xmlns:p7="http://
schemas.microsoft.com/ado/2009/02/edm/annotation">
<EntitySet Name="Products" EntityType="NorthwindModel.Product" />
<EntitySet Name="Suppliers"
EntityType="NorthwindModel.Supplier" />
<AssociationSet Name="FK_Products_Suppliers"
Association="NorthwindModel.FK_Products_Suppliers">
<End Role="Suppliers" EntitySet="Suppliers" />
<End Role="Products" EntitySet="Products" />
</AssociationSet>
</EntityContainer>
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
439
</Schema>
</edmx:DataServices>
</edmx:Edmx>
In the entity Products, you can see that an additional relation to the Supplier is available as a
NavigationProperty. A navigation property links two entities of an OData service and assigns the supplier to
the product here.
When using a real OData service, the interface would be available by calling the service URL directly in a browser
(e.g. http://services.odata.org/V3/Northwind/Northwind.svc/$metadata for the Northwind
OData test service). In our app project we use local mock data and serve the data with the mock server instead.
webapp/view/Worklist.view.xml
…
<columns>
<Column id="nameColumn">
<Text
id="nameColumnTitle"
text="{i18n>TableNameColumnTitle}"/>
</Column>
<Column
id="supplierNameColumn"
demandPopin="false"
minScreenWidth="Tablet">
<Text text="{i18n>TableSupplierColumnTitle}"/>
</Column>
<Column
id="unitPriceColumn"
hAlign="End"
demandPopin="true"
minScreenWidth="Tablet">
<Text text="{i18n>TablePriceColumnTitle}"/>
</Column>
<Column id="unitsOnOrderColumn"
demandPopin="true"
minScreenWidth="Tablet"
hAlign="End">
<Text text="{i18n>TableUnitsOrderedColumnTitle}"/>
</Column>
<Column id="unitsInStockColumn"
hAlign="End">
<Text text="{i18n>TableUnitsInStockColumnTitle}"/>
</Column>
</columns>
…
Next, we change the column definitions of the table. We define the new columns and update the existing ones in
the columns aggregation of the table according to the code above (i.e. just copy and paste the highlighted content
into your columns aggregation).
The column definitions include a text that we will later define in the resource bundle (i18n model – a short name
for internationalization) so that the column titles can be translated to other languages. And we will define
additional settings for text alignment and making the table responsive. Some columns are not as important as
others and can be displayed below the main columns (popin) on devices with small or medium-sized screens.
Let's have a detailed look at the columns:
● Product Name
The product name is the first column and it is always visible on any device.
440
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
● Supplier
Each product has a supplier. This column contains the company name of the supplier supplying the product.
On small screen devices like smart phones we hide this column as we do not have much screen space for a
table.
● Price
The currency of the product’s unit price is Euro (EUR). We are talking about stock levels in this app, so the
number of units is most interesting for us - not their price. Price is still good to know, so it is not entirely
removed. However, this field is not as important as the unit fields and will popin on smart phones.
● Units on Order
This column shows the units that have been ordered already for this product and will be added to the stock
shortly. In other words, this is the number of items ordered, but not yet received. A shortage for a product can
easily be resolved by reordering the product in advance (we add this feature later). This field will popin on
smart phone devices.
● Units in Stock
The column contains the product’s stock units currently available for sale. This field is the most important
column for our manage product stocks app. Therefore, this column is visible for all devices and it’s visible
without a popin. Later, we will use this column to visualize a stock status for the specific products so that
attention will be drawn to any stock issues with the products.
webapp/model/formatter.js
sap.ui.define([
"sap/ui/core/ValueState"
], function(ValueState) {
"use strict";
return {
numberUnit: function(sValue) {
…
},
/**
* Defines a value state based on the stock level
*
* @public
* @param {number} iValue the stock level of a product
* @returns {string} sValue the state for the stock level
*/
quantityState: function(iValue) {
if (iValue === 0) {
return ValueState.Error;
} else if (iValue <= 10) {
return ValueState.Warning;
} else {
return ValueState.Success;
}
}
}
};
Our table has a column that will contain the units in stock for each product. It would be nice to visualize the
corresponding numbers so that we can point out important information to the users, such as a shortage. We want
to visualize the numbers by using a specific ValueState depending on the units in stock. This can be achieved by
a simple formatter, which we will use later.
We add a new formatter function quantityState to the webapp/model/formatter.js file. The ValueState
type is loaded as an additional dependency. The formatter implements the following logic with a simple if/else
statement:
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
441
● A totally depleted stock (0 pieces remaining) will return a semantic Error state that will color the text in the
units in stock field red.
● Very low stock (10 or less pieces remaining) will lead to a Warning state (orange).
● A stock of more than 10 items will convert to Success (green)
webapp/view/Worklist.view.xml
…
<items>
<ColumnListItem
press="onPress"
type="Navigation">
<cells>
<ObjectIdentifier
title="{ProductName}"/>
<Text text = "{Supplier/CompanyName}"/>
<ObjectNumber
unit="EUR"
number="{
path: 'UnitPrice',
formatter: '.formatter.numberUnit'
}"/>
<ObjectNumber
number="{UnitsOnOrder}"
unit="PC"/>
<ObjectNumber
number="{UnitsInStock}"
unit="PC"
state="{
path: 'UnitsInStock',
formatter: '.formatter.quantityState'
}"/>
</cells>
</ColumnListItem>
</items>
…
The next task is to define the cells to appear in each row of the table. For each column, we define a control in the
cells aggregation of the table and configure the data binding as well as the formatting of the data.
● The first cell simply displays the ProductName property of the corresponding entity by using an
ObjectIdentifier control.
● The Supplier cell of each row is a simple sap.m.Text control. Its text property is bound to Supplier/
CompanyName. This references the property CompanyName of the entity’s NavigationProperty Supplier.
This NavigationProperty will be expanded automatically; we configured this earlier in this step.
● The Price cell uses an sap.m.ObjectNumber control and a custom formatter. You can find the formatter’s
implementation in the webapp/model/formatter.js file. The unit property is not bound and hard coded to
“EUR” as the currency is not part of the model for our app. The units on order are displayed with a
sap.m.ObjectNumber control as well, but without additional formatting. Its unit property is hard coded to PC,
which is the short form for "pieces".
● The last cell shows the units in stock and was already specified in the previous step. We would like to use this
field to show an additional status based on the stock level so we change the binding syntax to an object
notation and add an additional formatter quantityState. We implemented this formatter in the previous
code block above.
442
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Note
The formatter functions used in this XML view are loaded by the controller and thus can be accessed relatively
to the controller through the property name .formatter. This logic is already part of the initial app.
webapp/i18n/i18n.properties
#XTIT: The title of the column containing Product name
TableProductColumnTitle=Product
#XTIT: The title of the column containing Supplier name
TableSupplierColumnTitle=Supplier
#XTIT: The title of the column containing Price
TablePriceColumnTitle=Price
#XTIT: The title of the column containing Ordered Units
TableUnitsOrderedColumnTitle=Units Ordered
#XTIT: The title of the column containing Units in Stock
TableUnitsInStockColumnTitle=Units in Stock
#XBLI: text for a table with no data
tableNoDataText=No products are currently available
...
Finally, we modify the existing column names in the resource bundle file webapp/i18n/i18n.properties to
match our scenario and add the new texts for the column titles.
Note
The webapp/i18n/i18n.properties file contains some annotations for each key in the file. These
annotations offer some more context which can help translators to better interpret the semantics of the text
belonging to the keys. An example for such an annotation is XTIT in the i18n.properties file above, which
tells that the corresponding key is supposed to be used as a title. The guidelines at https://github.com/SAP/
openui5/blob/master/docs/guidelines/translationfiles.md give you a better idea of how this can be used. Be
aware that this is how SAP uses the annotations internally. In case you want to use this approach to work with
your own translators make sure to discuss about a common set of allowed annotations that everybody
understands.
Tip
Testing the Responsiveness of the App
In the previous code blocks of this step we made sure that our table is responsive. Depending on the device type
columns are hidden, displayed as a popin, or displayed without a popin. Now, we want to test the
responsiveness without the having different devices. This can be done in different ways, we will cover two
options:
● Testing the responsiveness with the SAP Web IDE
SAP Web IDE can simulate different screen sizes. You just have to make sure that the run configuration is
set up correctly:
1. In SAP Web IDE, choose
Run
New Configuration
2. Select the Run with MockServer configuration.
3. In the Frame screen area, select the Open with frame checkbox.
If you create a new project this option is switched on by default. For apps created based on the Worklist
template, however, this option is switched off to make it easier to debug the application coding.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
443
4. Save the configuration and close the dialog.
If you now run the application again, you will see the surrounding frame, in which you can easily choose
between different screen sizes and change the device orientation.
Switch, for example from Medium to Small, and you will see that the table behaves as expected.
Figure 131: Testing the responsiveness in SAP Web IDE
● Testing the responsiveness using the Developer Tools of Google Chrome
If you use the Google Chrome browser, you can also use its great developer tools to test the responsiveness
of your app.
1. Call the app and open the developer tools in Chrome with F12
2. Choose the Toggle device mode icon.
3. Now choose from the different devices in the Models field, and observe the behavior of your app.
444
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Step 4: Quick Filter for the Worklist
For easily detecting and managing product shortages in our app, we will add a quick filter for the worklist table.
Users can press the filter tabs to display the products according to whether they are in stock, have low stock or no
stock. The table will update accordingly and show only the products matching the criteria.
Preview
Figure 132: A quick filter allows filtering the product table
Coding
You can view and download all files in the Demo Kit at Worklist App - Step 4.
webapp/view/Worklist.view.xml
…
<Table …>
<!--<headerToolbar>
<Toolbar>
<Title
id="tableHeader"
text="{worklistView>/worklistTableTitle}"/>
<ToolbarSpacer />
<SearchField
id="searchField"
tooltip="{i18n>worklistSearchTooltip}"
search="onSearch"
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
445
…
width="auto"/>
</Toolbar>
</headerToolbar>-->
<columns>
As a preparation step, comment out the header toolbar of the table in the view.
webapp/view/Worklist.view.xml
<mvc:View
controllerName="myCompany.myApp.controller.Worklist"
xmlns:mvc="sap.ui.core.mvc"
xmlns:semantic="sap.m.semantic"
xmlns="sap.m">
<semantic:FullscreenPage
id="page"
navButtonPress="onNavBack"
showNavButton="true"
title="{i18n>worklistViewTitle}">
<semantic:content>
<IconTabBar
id="iconTabBar"
select="onQuickFilter"
expandable="false"
applyContentPadding="false">
<items>
<IconTabFilter
key="all"
showAll="true"
count="{worklistView>/countAll}"
text="{i18n>WorklistFilterProductsAll}"/>
<IconTabSeparator/>
<IconTabFilter
key="inStock"
icon="sap-icon://message-success"
iconColor="Positive"
count="{worklistView>/inStock}"
text="{i18n>WorklistFilterInStock}"/>
<IconTabFilter
key="shortage"
icon="sap-icon://message-warning"
iconColor="Critical"
count="{worklistView>/shortage}"
text="{i18n>WorklistFilterShortage}"/>
<IconTabFilter
key="outOfStock"
icon="sap-icon://message-error"
iconColor="Negative"
count="{worklistView>/outOfStock}"
text="{i18n>WorklistFilterOutOfStock}"/>
</items>
<content>
<Table
id="table"
busyIndicatorDelay="{worklistView>/tableBusyDelay}"
class="sapUiResponsiveMargin sapUiNoMarginTop"
growing="true"
growingScrollToLoad="true"
noDataText="{worklistView>/tableNoDataText}"
updateFinished="onUpdateFinished"
width="auto"
items="{
path: '/Products',
sorter: {
path: 'ProductName',
descending: false
446
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
},
parameters: {
'expand': 'Supplier'
}
}">
<headerToolbar>
<Toolbar>
<Title
id="tableHeader"
text="{worklistView>/worklistTableTitle}"/>
<ToolbarSpacer/>
<SearchField
id="searchField"
search="onSearch"
tooltip="{i18n>worklistSearchTooltip}"
width="auto"/>
</Toolbar>
</headerToolbar>
<columns>
<Column id="nameColumn">
<Text
id="nameColumnTitle"
text="{i18n>TableNameColumnTitle}"/>
</Column>
<Column
id="supplierNameColumn"
demandPopin="false"
minScreenWidth="Tablet">
<Text text="{i18n>TableSupplierColumnTitle}"/>
</Column>
<Column
id="unitPriceColumn"
hAlign="End"
demandPopin="true"
minScreenWidth="Tablet">
<Text text="{i18n>TablePriceColumnTitle}"/>
</Column>
<Column id="unitsOnOrderColumn"
demandPopin="true"
minScreenWidth="Tablet"
hAlign="End">
<Text text="{i18n>TableUnitsOrderedColumnTitle}"/>
</Column>
<Column id="unitsInStockColumn"
hAlign="End">
<Text text="{i18n>TableUnitsInStockColumnTitle}"/>
</Column>
</columns>
<items>
<ColumnListItem
press="onPress"
type="Navigation">
<cells>
<ObjectIdentifier
title="{ProductName}"/>
<Text text = "{Supplier/CompanyName}"/>
<ObjectNumber
unit="EUR"
number="{
path: 'UnitPrice',
formatter: '.formatter.numberUnit'
}"/>
<ObjectNumber
number="{UnitsOnOrder}"
unit="PC"/>
<ObjectNumber
number="{UnitsInStock}"
unit="PC"
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
447
state="{
path: 'UnitsInStock',
formatter: '.formatter.quantityState'
}"/>
</cells>
</ColumnListItem>
</items>
</Table>
</content>
</IconTabBar>
</semantic:content>
<semantic:sendEmailAction>
<semantic:SendEmailAction
id="shareEmail"
press="onShareEmailPress"/>
</semantic:sendEmailAction>
</semantic:FullscreenPage>
</mvc:View>
We now update the view and add the new UI for the quick filter to the content aggregation of the
sap.m.SemanticPage control just before the table. It is modeled using a sap.m.IconTabBar control and a
sap.m.IconTabFilter for each of the following filter options:
● Total Stock
This tab will simply show the overall number of products that has been returned by the data service. The
count property is bound to a local view model and the number will be updated in the controller later in this
step. This tab will show a larger number only (optional) and no icon by using the showAll property.
● Out of Stock
This tab will show all the products that are out of stock. We choose a matching icon from the icon font and set
the icon color to the semantic Negative state so that it will appear in red.
● Shortage
This tab will show products that have less than 10 pieces remaining with a semantic Critical state that will
make the icon appear in orange. The count of the number of low stock products will be displayed on the tab
and the icon will appear in orange.
● Plenty in Stock
This tab will show products that have more than 10 pieces in stock. The semantic Positive state will let the
icon appear in green. As usual the UI texts for the tabs are linked to the resource bundle file and will be added
later. Do not forget to set the standard CSS class sapUiNoMarginTop on the table to remove the spacing
between the IconTabBar and the table and make the UI look nicer.
Note
Each IconTabFilter element has a key property that is used to identify the tab that was pressed in the
event handler onQuickFilter that is registered on the IconTabBar control directly. The event handler
implementation does the actual filtering on the table and is defined in the controller.
webapp/controller/Worklist.controller.js
...
onInit: function() {
var oViewModel,
iOriginalBusyDelay,
oTable = this.byId("table");
// Put down worklist table's original value for busy indicator delay,
// so it can be restored later on. Busy handling on the table is
// taken care of by the table itself.
iOriginalBusyDelay = oTable.getBusyIndicatorDelay();
448
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
this._oTable = oTable;
// keeps the search state
this._oTableSearchState = [];
// Model used to manipulate control states
oViewModel = new JSONModel({
worklistTableTitle: this.getResourceBundle().getText("worklistTableTitle"),
saveAsTileTitle: this.getResourceBundle().getText("worklistViewTitle"),
shareOnJamTitle: this.getResourceBundle().getText("worklistViewTitle"),
shareSendEmailSubject:
this.getResourceBundle().getText("shareSendEmailWorklistSubject"),
shareSendEmailMessage:
this.getResourceBundle().getText("shareSendEmailWorklistMessage", [location.href]),
tableNoDataText: this.getResourceBundle().getText("tableNoDataText"),
tableBusyDelay: 0,
inStock: 0,
shortage: 0,
outOfStock: 0,
countAll: 0
});
this.setModel(oViewModel, "worklistView");
// Create an object of filters
this._mFilters = {
"inStock": [new sap.ui.model.Filter("UnitsInStock", "GT", 10)],
"outOfStock": [new sap.ui.model.Filter("UnitsInStock", "LE", 0)],
"shortage": [new sap.ui.model.Filter("UnitsInStock", "BT", 1, 10)],
"all": []
};
// Make sure, busy indication is showing immediately so there is no
// break after the busy indication for loading the view's meta data is
// ended (see promise 'oWhenMetadataIsLoaded' in AppController)
oTable.attachEventOnce("updateFinished", function() {
// Restore original busy indicator delay for worklist's table
oViewModel.setProperty("/tableBusyDelay", iOriginalBusyDelay);
});
},
...
As a preparation step for the filter tabs we add properties for the counters into the local view model of the worklist
controller. We initialize the four values with 0 each. Furthermore, we create an object _mFilters that contains a
filter for each tab. We will use the filters for filtering the table below the tabs. The properties in _mFilters
correlate to the keys of the IconTabFilter controls we defined above in the Worklist.view.xml file. This way
we can easily access a filter for a given tab based on the key of the corresponding tab.
Creating a simple filter requires a binding path as first parameter of the filter constructor (e.g. "UnitsInStock"),
a filter operator (e.g. "GT") as second argument, and a value to compare (e.g. 10) as the third argument. We
create such filters for all three tabs with different filter operators as described in the view part above. Additionally,
we create an all filter which is an empty array for clearing the binding again (when the user chooses the All tab).
webapp/controller/Worklist.controller.js
...
onUpdateFinished: function(oEvent) {
// update the worklist's object counter after the table update
var sTitle,
oTable = oEvent.getSource(),
oViewModel = this.getModel("worklistView"),
iTotalItems = oEvent.getParameter("total");
// only update the counter if the length is final and
// the table is not empty
if (iTotalItems && oTable.getBinding("items").isLengthFinal()) {
sTitle = this.getResourceBundle().getText("worklistTableTitleCount",
[iTotalItems]);
// Get the count for all the products and set the value to 'countAll' property
this.getModel().read("/Products/$count", {
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
449
success: function (oData) {
oViewModel.setProperty("/countAll", oData);
}
});
// read the count for the unitsInStock filter
this.getModel().read("/Products/$count", {
success: function (oData) {
oViewModel.setProperty("/inStock", oData);
},
filters: this._mFilters.inStock
});
// read the count for the outOfStock filter
this.getModel().read("/Products/$count", {
success: function(oData){
oViewModel.setProperty("/outOfStock", oData);
},
filters: this._mFilters.outOfStock
});
// read the count for the shortage filter
this.getModel().read("/Products/$count", {
success: function(oData){
oViewModel.setProperty("/shortage", oData);
},
filters: this._mFilters.shortage
});
} else {
sTitle = this.getResourceBundle().getText("worklistTableTitle");
}
this.getModel("worklistView").setProperty("/worklistTableTitle", sTitle);
},
...
In the onUpdateFinished function, we get the count of all products by triggering a read operation on the model
with the appropriate filter. The filter is a helper object of OpenUI5 that defines the condition for each tab on the
data binding level. We already created the filters in the onInit function.
Note
The v2.ODataModel will automatically bundle these read requests to one batch request to the server (if
batch mode is enabled).
In the success handler of each read operation we update the corresponding property in the view model with the
real count of the matching items that were returned by the service.
webapp/controller/Worklist.controller.js
...
_applySearch: function(oTableSearchState) {
...
},
/**
* Event handler when a filter tab gets pressed
* @param {sap.ui.base.Event} oEvent the filter tab event
* @public
*/
onQuickFilter: function(oEvent) {
var oBinding = this._oTable.getBinding("items"),
sKey = oEvent.getParameter("selectedKey");
oBinding.filter(this._mFilters[sKey]);
}
...
450
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Next, we implement the handler for the select event of the IconTabBar. In this event handler we get a reference
to the binding for the items aggregation of our table and store it in the variable oBinding. Then we read the
parameter selectedKey from the event object to find out which tab has been selected. This selectedKey is
used to get the correct filter for the selected tab. Next, we simply call filter method on oBinding and pass the
correct filter of the selected tab.
The filters are always applied as an array on the binding level, so you don't need to take care of managing the data,
the data binding features of OpenUI5 will automatically take care.
webapp/i18n/i18n.properties
...
#XTIT: The title of the products quick filter
WorklistFilterProductsAll=Products
#XTIT: The title of the out of stock products filter
WorklistFilterOutOfStock=Out of Stock
#XTIT: The title of the low stock products filter
WorklistFilterShortage=Shortage
#XTIT: The title of the products in stock filter
WorklistFilterInStock=Plenty in Stock
#~~~ Object View ~~~~~~~~~~~~~~~~~~~~~~~~~~
...
We finally add the texts for the tab filters to the resource bundle. Copy the text definitions from the code section
above to the end of the Worklistn View section in the i18n file.
Now run the app again and click on the filter icons on top of the table. The products should be filtered according to
the selection in the filter bar and the count should match the number of items displayed.
Related Information
API Reference: sap.ui.model.ListBinding.filter
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
451
Step 5: Adding Actions to the Worklist
Now we can easily spot shortages on our stock, but we also would like to take action and resolve it. Either we can
decide to remove the product until the shortage is resolved or order new items of the product. In this step, we will
add these actions to the footer of the worklist table.
Preview
Figure 133: Actions are now available in the footer bar
Coding
You can view and download all files in the Samples in the Demo Kit at Worklist App - Step 5.
webapp/view/Worklist.view.xml
...
<Table
id="table"
busyIndicatorDelay="{worklistView>/tableBusyDelay}"
class="sapUiResponsiveMargin sapUiNoMarginTop"
growing="true"
growingScrollToLoad="true"
noDataText="{worklistView>/tableNoDataText}"
updateFinished="onUpdateFinished"
width="auto"
mode="MultiSelect"
items="{
452
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
path: '/Products',
sorter: {
path: 'ProductName',
descending: false
},
parameters: {
'expand': 'Supplier'
}
}">
...
We change the table mode to MultiSelect. This allows to select multiple items in the table. Below, we will add two
buttons to the footer bar of the screen. The first button will add to the UnitsInStock property, and the second
will remove the selected products.
webapp/view/Worklist.view.xml
<mvc:View
controllerName="myCompany.myApp.controller.Worklist"
xmlns:mvc="sap.ui.core.mvc"
xmlns:semantic="sap.m.semantic"
xmlns="sap.m">
<semantic:FullscreenPage
id="page"
navButtonPress="onNavBack"
showNavButton="true"
title="{i18n>worklistViewTitle}">
<semantic:content>
...
</semantic:content>
<semantic:sendEmailAction>
<semantic:SendEmailAction
id="shareEmail"
press="onShareEmailPress"/>
</semantic:sendEmailAction>
<semantic:positiveAction>
<semantic:PositiveAction text="{i18n>TableProductsReorder}"
press="onUpdateStockObjects"/>
</semantic:positiveAction>
<semantic:negativeAction>
<semantic:NegativeAction text="{i18n>TablePorductsUnlist}"
press="onUnlistObjects"/>
</semantic:negativeAction>
</semantic:FullscreenPage>
</mvc:View>
...
Now we add the buttons to the footer bar of the page. The two semantic actions Negative and Positive will
automatically be positioned in the footer bar. The first button will order new items of the selected products and the
second one will remove them. The corresponding event handlers will be implemented in the controller.
webapp/controller/Worklist.controller.js
sap.ui.define([
"myCompany/myApp/controller/BaseController",
"sap/ui/model/json/JSONModel",
"myCompany/myApp/model/formatter",
"sap/ui/model/Filter",
"sap/ui/model/FilterOperator",
"sap/m/MessageToast",
"sap/m/MessageBox"
], function(BaseController, JSONModel, formatter, Filter, FilterOperator,
MessageToast, MessageBox) {
"use strict";
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
453
...
return BaseController.extend("myCompany.myApp.controller.Worklist", {
formatter: formatter,
/**
* Displays an error message dialog. The displayed dialog is content
density aware.
* @param {string} sMsg The error message to be displayed
* @private
*/
_showErrorMessage: function(sMsg) {
MessageBox.error(sMsg, {
styleClass: this.getOwnerComponent().getContentDensityClass()
});
},
/**
* Event handler when a filter tab gets pressed
* @param {sap.ui.base.Event} oEvent the filter tab event
* @public
*/
onQuickFilter: function(oEvent) {
var oBinding = this._oTable.getBinding("items"),
sKey = oEvent.getParameter("selectedKey");
oBinding.filter(this._mFilters[sKey]);
},
/**
* Error and success handler for the unlist action.
* @param {string} sProductId the product ID for which this handler is
called
* @param {boolean} bSuccess true in case of a success handler, else false
(for error handler)
* @param {number} iRequestNumber the counter which specifies the position
of this request
* @param {number} iTotalRequests the number of all requests sent
* @private
*/
_handleUnlistActionResult : function (sProductId, bSuccess, iRequestNumber,
iTotalRequests){
// we could create a counter for successful and one for failed requests
// however, we just assume that every single request was successful and
display a success message once
if (iRequestNumber === iTotalRequests) {
MessageToast.show(this.getModel("i18n").getResourceBundle().getText("StockRemovedSuc
cessMsg", [iTotalRequests]));
}
},
called
/**
* Error and success handler for the reorder action.
* @param {string} sProductId the product ID for which this handler is
* @param {boolean} bSuccess true in case of a success handler, else false
(for error handler)
* @param {number} iRequestNumber the counter which specifies the position
of this request
* @param {number} iTotalRequests the number of all requests sent
* @private
*/
_handleReorderActionResult : function (sProductId, bSuccess,
iRequestNumber, iTotalRequests){
// we could create a counter for successful and one for failed requests
// however, we just assume that every single request was successful and
display a success message once
if (iRequestNumber === iTotalRequests) {
MessageToast.show(this.getModel("i18n").getResourceBundle().getText("StockUpdatedSuc
cessMsg", [iTotalRequests]));
}
454
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
},
/**
* Event handler for the unlist button. Will delete the
* product from the (local) model.
* @public
*/
onUnlistObjects: function() {
var aSelectedProducts, i, sPath, oProduct, oProductId;
aSelectedProducts = this.byId("table").getSelectedItems();
if (aSelectedProducts.length) {
for (i = 0; i < aSelectedProducts.length; i++) {
oProduct = aSelectedProducts[i];
oProductId =
oProduct.getBindingContext().getProperty("ProductID");
sPath = oProduct.getBindingContext().getPath();
this.getModel().remove(sPath, {
success : this._handleUnlistActionResult.bind(this,
oProductId, true, i+1, aSelectedProducts.length),
error : this._handleUnlistActionResult.bind(this,
oProductId, false, i+1, aSelectedProducts.length)
});
}
} else {
this._showErrorMessage(this.getModel("i18n").getResourceBundle().getText("TableSelec
tProduct"));
}
},
/**
* Event handler for the reorder button. Will reorder the
* product by updating the (local) model
* @public
*/
onUpdateStockObjects: function() {
var aSelectedProducts, i, sPath, oProductObject;
aSelectedProducts = this.byId("table").getSelectedItems();
if (aSelectedProducts.length) {
for (i = 0; i < aSelectedProducts.length; i++) {
sPath = aSelectedProducts[i].getBindingContext().getPath();
oProductObject =
aSelectedProducts[i].getBindingContext().getObject();
oProductObject.UnitsInStock += 10;
this.getModel().update(sPath, oProductObject, {
success : this._handleReorderActionResult.bind(this,
oProductObject.ProductID, true, i+1, aSelectedProducts.length),
error : this._handleReorderActionResult.bind(this,
oProductObject.ProductID, false, i+1, aSelectedProducts.length)
});
}
} else {
this._showErrorMessage(this.getModel("i18n").getResourceBundle().getText("TableSelec
tProduct"));
}
}
});
});
Let’s have a look at the implementation of the event handlers for the new actions. We first load the
sap.m.MessageToast control as a new dependency to display a success message for the unlist and reorder
actions.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
455
Both actions are similar from an implementation perspective and the details are described below. They both loop
over the selected items in the table and trigger a model update or deletion on the selected path. After that, a
success message with the number of products processed is displayed. The table is updated automatically by the
model change.
● Order
For each of the selected items the binding path in the model is retrieved by calling the helper method
getBindingContextPath on the selected item. Additionally, the data object from the model is fetched by
calling getBindingContext().getObject() on the item. We update the data object and simply add 10
items to the stock to keep things simple in this example. Then we call the update function on the model with
the product path and the new object. This will trigger an OData update request to the back end and a refresh of
the model afterwards (multiple requests are handled together in batch mode). When the model refreshes, the
table will be updated as well because of its binding.
● Remove
For each of the selected items the binding path in the model is retrieved by calling the helper method
getBindingContextPath on the selected item. Then, we call the remove function on the model with the
product path. This triggers an OData delete request to the back end and a refresh of the OData model
afterwards. Again, when the model is refreshed, the table will be updated as well because of its binding. The
ODataModel v2 collects all these requests and only sends one batch request (this default behavior can be
changed).
For each action we register both a success handler and an error handler. The success handler and error
handler for each action is the same, but the function is called with different parameters. This allows us to use the
same handler function for both the error and success case. Inside the corresponding handlers we simply display a
success message once by comparing the current request number with the total number of requests. Furthermore,
we assume that all of our requests always succeed.
In a real scenario, you could have a counter for error responses, and one for success responses. Finally, you could
implement you own business logic for error and success cases, like displaying the number of failed and succeeded
requests together with the corresponding product identified by the product ID parameter of the handlers. We don’t
do this to keep things simple.
Note
In our example, the remove or order actions are only applied to items that are visible in the table, even if the
Select All checkbox of the table is selected. Keep in mind that there may be more data on the back end which is
currently not loaded, and therefore it is neither displayed and nor can it be selected by the user.
If you want to change this behavior, you might need to change both back-end and front-end code.
webapp/controller/Object.controller.js
...
/*global location*/
sap.ui.define([
"myCompany/myApp/controller/BaseController",
"sap/ui/model/json/JSONModel",
"sap/ui/core/routing/History",
"myCompany/myApp/model/formatter"
], function(BaseController, JSONModel, History, formatter) {
"use strict";
return BaseController.extend("myCompany.myApp.controller.Object", {
formatter: formatter,
...
/**
* Event handler for navigating back.
456
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
* It checks if there is a history entry. If yes, history.go(-1) will happen.
* If not, it will replace the current entry of the browser history with the
worklist route.
* Furthermore, it removes the defined binding context of the view by calling
unbindElement().
* @public
*/
onNavBack: function() {
var oHistory = History.getInstance();
var sPreviousHash = oHistory.getPreviousHash();
this.getView().unbindElement();
if (sPreviousHash !== undefined) {
// The history contains a previous entry
history.go(-1);
} else {
// Otherwise we go backwards with a forward history
var bReplace = true;
this.getRouter().navTo("worklist", {}, bReplace);
}
},
...
});
When we navigate to the detail page of a product the view is bound to selected Product entity of our OData
model. Choosing the navigation button on the detail page will navigate back to the worklist page, the start page of
our app. If we would now remove the same product which we just saw in the detail page we discover a strange
behavior: Our NotFound page is displayed.
This happens because we are removing the entity from the model and the back end, but there is still an existing
binding for that product. The HTTP request still gets the product, but it is not available on the back end anymore.
Therefore the back end returns an HTTP 404 response, which fires a BindingChange event. This event is still
handled in our webapp/controller/Object.controller.js file, even though the object page is currently not
displayed. Because the product was deleted, there is no data available. Therefore the event handler simply
displays the objectNotFound target.
To prevent this, we simply call unbindElement() on the view whenever the user chooses the Back button on the
detail page.
webapp/i18n/i18n.properties
...
#text of the button for Products reordering
TableProductsReorder=Order
#text for the button for Products unlisting
TablePorductsUnlist=Remove
#Text for no product selected
TableNoProductsSelected=No product selected
#Product successfully deleted
StockRemovedSuccessMsg=Product removed
#Product successfully updated
StockUpdatedSuccessMsg=Product stock level updated
#~~~ Object View ~~~~~~~~~~~~~~~~~~~~~~~~~~
...
Add the missing texts for the buttons and the message toast.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
457
Save the changes and run the application again. Try the Order and Remove buttons with one or more products
selected. The stock value will be increased or the product will be (temporarily) removed from the worklist table. As
all of our changes happen on a local mock server, we can simply reload the app to reset the data again.
Step 6: Extending the Detail Page
In this step, we will extend the detail page of our app to show more information of a given product with various UI
controls. We will enrich the header area and display further attributes in an info panel for information about the
supplier.
Preview
Figure 134: Detail page with more product information
Coding
You can view and download all files in the Samples in the Demo Kit at Worklist App - Step 6 .
webapp/view/Object.view.xml
<mvc:View
controllerName="myCompany.myApp.controller.Object"
xmlns:mvc="sap.ui.core.mvc"
xmlns:semantic="sap.m.semantic"
xmlns:form="sap.ui.layout.form"
xmlns="sap.m">
<semantic:FullscreenPage
id="page"
busy="{objectView>/busy}"
busyIndicatorDelay="{objectView>/delay}"
navButtonPress="onNavBack"
showNavButton="true"
title="{i18n>objectTitle}">
<semantic:content>
<ObjectHeader
id="objectHeader"
responsive="true"
title="{ProductName}"
458
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
numberUnit="PC"
numberState="{
path: 'UnitsInStock',
formatter: '.formatter.quantityState'
}"
number="{UnitsInStock}">
<attributes>
<ObjectAttribute
title="{i18n>ObjectProductIdText}"
text="{ProductID}"/>
<ObjectAttribute
title="{i18n>ObjectPriceTitle}"
text="{
path: 'UnitPrice',
formatter: '.formatter.numberUnit'
} EUR"/>
</attributes>
<statuses>
<ObjectStatus
text="{i18n>ObjectDiscontinuedStatusText}"
state="Error"
visible="{path:'Discontinued'}"/>
<ProgressIndicator
percentValue="{UnitsInStock}"
displayValue="{UnitsInStock}"
showValue="true"
state="{
path: 'UnitsInStock',
formatter: '.formatter.quantityState'}" />
</statuses>
</ObjectHeader>
<Panel
class="sapUiNoContentPadding"
headerText="{i18n>ObjectSupplierTabTitle}">
<content>
<form:SimpleForm
minWidth="1024"
maxContainerCols="2"
editable="false"
layout="ResponsiveGridLayout"
labelSpanL="3"
labelSpanM="3"
emptySpanL="4"
emptySpanM="4"
columnsL="1"
columnsM="1">
<form:content>
<Label text="{i18n>ObjectSupplierName}"/>
<Text text="{Supplier/CompanyName}"/>
<Label text="{i18n>ObjectSupplierAddress}"/>
<Text text="{Supplier/Address}"/>
<Label text="{i18n>ObjectSupplierZipcode} /
{i18n>ObjectSupplierCity}"/>
<Text text="{Supplier/PostalCode} / {Supplier/City}"/>
<Label text="{i18n>ObjectSupplierCountry}"/>
<Text text="{Supplier/Country}"/>
</form:content>
</form:SimpleForm>
</content>
</Panel>
</semantic:content>
<semantic:sendEmailAction>
<semantic:SendEmailAction
id="shareEmail"
press="onShareEmailPress"/>
</semantic:sendEmailAction>
</semantic:FullscreenPage>
</mvc:View>
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
459
Initially, the detail page content only consists of an sap.m.ObjectHeader control that displays the product name
and its stock. We will make it more flexible for small screen devices by setting the responsive property. As you have
seen in the table example before, some OpenUI5 controls contain features to tweak and configure the
responsiveness. Additionally, we add the same information that is shown in the worklist table to the detail page.
Next, we define some additional attributes for the product with two sap.m.ObjectAttribute controls, one for
the Price and one for the ProductID. These are important product attributes for us, so we want to include them
in our header area.
To get a better visual representation of the current stock of the shown product, we use the control statuses. It can
be used for ObjectStatus and for ProgressIndicator. We want to use both. The ObjectStatus shows up as
Discontinued, if our product will not be produced anymore. The ProgressIndicator uses the same formatter
function as our UnitsInStock in the numberState of the ObjectHeader.
Below the object header we can use sap.m.Panel to display some additional information in a nice layout on the
page. Inside the panel we use sap.ui.layout.form.SimpleForm to have the labels and texts we want to
display aligned to each other.
webapp/i18n/i18n.properties
...
#Price per unit text
ObjectPriceTitle=Price
#Discontinued text
ObjectDiscontinuedStatusText=Discontinued
#Supplier tab title
ObjectSupplierTabTitle=Supplier Info
#Supplier company name
ObjectSupplierName=Name
#Supplier contact person name
ObjectSupplierContact=Contact
#Supplier contact address
ObjectSupplierAddress=Address
#Supplier zip code
ObjectSupplierZipcode=ZIP Code
#Supplier city name
ObjectSupplierCity=City
#Supplier country
ObjectSupplierCountry=Country
#Object Product ID text
ObjectProductIdText=Product ID
#~~~ Footer Options ~~~~~~~~~~~~~~~~~~~~~~~
...
As before, we add new i18n texts to the resource bundle.
Save all the changes and run the application. Click on any product and see the product details displayed on the
detail page.
460
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Step 7: Adding a Comments Section
In this step, we extend the product detail view by adding a feature allowing to add comments to the product.
Preview
Figure 135: Comments section added to the detail page
Coding
You can view and download all files in the Samples in the Demo Kit at Worklist App - Step 7 .
webapp/view/Object.view.xml
<mvc:View
controllerName="myCompany.myApp.controller.Object"
xmlns:mvc="sap.ui.core.mvc"
xmlns:semantic="sap.m.semantic"
xmlns:form="sap.ui.layout.form"
xmlns="sap.m">
<semantic:FullscreenPage
id="page"
busy="{objectView>/busy}"
busyIndicatorDelay="{objectView>/delay}"
navButtonPress="onNavBack"
showNavButton="true"
title="{i18n>objectTitle}">
<semantic:content>
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
461
[...}
[...]
[...]
<ObjectHeader
id="objectHeader"
responsive="true"
title="{ProductName}"
numberUnit="PC"
numberState="{
path: 'UnitsInStock',
formatter: '.formatter.quantityState'
}"
number="{UnitsInStock}">
<attributes>
</attributes>
<statuses>
</statuses>
</ObjectHeader>
<Panel
class="sapUiNoContentPadding"
headerText="{i18n>ObjectSupplierTabTitle}">
<content>
</content>
</Panel>
<Panel
class="sapUiContentPadding"
headerText="{i18n>ObjectCommentsTabTitle}">
<content>
<FeedInput post="onPost"/>
<List
id="idCommentsList"
noDataText="{i18n>ObjectCommentNoData}"
showSeparators="Inner"
items="{
path: 'productFeedback>/productComments',
sorter: {
path: 'date',
descending: true
}
}">
<FeedListItem
info="{productFeedback>type}"
text="{productFeedback>comment}"
timestamp="{productFeedback>date}"/>
</List>
</content>
</Panel>
</semantic:content>
<semantic:sendEmailAction>
<semantic:SendEmailAction
id="shareEmail"
press="onShareEmailPress"/>
</semantic:sendEmailAction>
</semantic:FullscreenPage>
</mvc:View>
Below the already existing panel, we add another panel that will serve as a container for our comments section.
Inside the new panel we add a sap.m.FeedInput control and attach an event handler onPost for the post event.
This control will render an input field and a button which allow users to post comments. The event handler we
registered will be implemented below.
Below the FeedInput control, we add a list with all existing comments. The items aggregation of the list is bound
to the /productComments property of the named model productFeedback that we will create below. All
comments shall be displayed in descending order based on their publishing date. Therefore, we also configure a
sorter for our items in the list.
462
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
The template for each row is a FeedListItem control. We configure the FeedListItem to simply display the date
of the post, the text of the post itself, and the type of the post.
webapp/controller/Object.controller.js
...
/*global location*/
sap.ui.define([
"myCompany/myApp/controller/BaseController",
"sap/ui/model/json/JSONModel",
"sap/ui/core/routing/History",
"myCompany/myApp/model/formatter",
"sap/ui/core/format/DateFormat",
"sap/ui/model/Filter",
"sap/ui/model/FilterOperator"
], function(BaseController, JSONModel, History, formatter, DateFormat, Filter,
FilterOperator) {
"use strict";
return BaseController.extend("myCompany.myApp.controller.Object", {
formatter: formatter,
...
_onBindingChange: function(oEvent) {
...
// Update the comments in the list
var oList = this.byId("idCommentsList");
var oBinding = oList.getBinding("items");
oBinding.filter(new Filter("productID", FilterOperator.EQ, sObjectId));
},
/**
* Updates the model with the user comments on Products.
* @function
* @param {sap.ui.base.Event} oEvent object of the user input
*/
onPost: function (oEvent) {
var oFormat = DateFormat.getDateTimeInstance({style: "medium"});
var sDate = oFormat.format(new Date());
var oObject = this.getView().getBindingContext().getObject();
var sValue = oEvent.getParameter("value");
var oEntry = {
productID: oObject.ProductID,
type: "Comment",
date: sDate,
comment: sValue
};
// update model
var oFeedbackModel = this.getModel("productFeedback");
var aEntries = oFeedbackModel.getData().productComments;
aEntries.push(oEntry);
oFeedbackModel.setData({
productComments : aEntries
});
}
});
});
First, we add three new dependencies to the controller. We need these dependencies because we want to create a
filter for the list and because we format the date and time of each post.
Whenever the binding of the detail view changes, we want to make sure that the comments for the current product
are displayed. Therefore, we change the private function _onBindingChange and update the filter of the list that
displays the comments by getting a reference to the binding of the items aggregation of our list and calling the
filter() API afterwards. The filter is passed on to the filter() API. We use the productID as fiilter criterium,
because we only want comments for a specific product.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
463
Next, the event handler for the post event of the FeedInput is implemented. In the onPost handler, we create a
new entry object that contains all data we want to store in our model. This data is the productId, the type of
the post (hard-coded in our example), the current date in a medium date format, and the comment itself. The
comment is retrieved from the event object. The productId is determined by calling getObject() on the view’s
binding context.
Finally, the new entry is added to the named model called productFeedback. This model does not exist yet, so
let’s create it next.
webapp/model/models.js
sap.ui.define([
"sap/ui/model/json/JSONModel",
"sap/ui/Device"
], function(JSONModel, Device) {
"use strict";
return {
createDeviceModel: function() {
var oModel = new JSONModel(Device);
oModel.setDefaultBindingMode("OneWay");
return oModel;
},
createCommentsModel: function() {
return new JSONModel({ productComments : [] });
}
};
});
In both the object view (detail page) as well as in the corresponding controller we used a named model called
productFeedback. In our example this model is a simple JSONModel. It is created in the function
createCommentsModel() in the model.js file. As you can see above, the function simply returns a new
instance of a JSONModel with a simple data object. The property productComments is an empty array and it will
be updated every time someone posts a new comment.
However, this model is not yet accessible throughout our app. Let’s fix this next.
webapp/Component.js
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/Device",
"myCompany/myApp/model/models",
"myCompany/myApp/controller/ErrorHandler"
], function(UIComponent, Device, models, ErrorHandler) {
"use strict";
return UIComponent.extend("myCompany.myApp.Component", {
...
init: function() {
// call the base component's init function
UIComponent.prototype.init.apply(this, arguments);
// initialize the error handler with the component
this._oErrorHandler = new ErrorHandler(this);
// set the device model
this.setModel(models.createDeviceModel(), "device");
// set the product feedback model
this.setModel(models.createCommentsModel(), "productFeedback");
// create the views based on the url/hash
this.getRouter().initialize();
},
...
});
464
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
});
Now it’s time to make the named model productFeedback available to our app. Therefore, just change the init
function of our Component.js file by calling our createCommentsModel() method and setting the returned
model on the component. After this, our model is accessible in our app.
webapp/i18n/i18n.properties
...
#Comments tab title
ObjectCommentsTabTitle=Comments
#No comments text
ObjectCommentNoData=No Comments
#~~~ Footer Options ~~~~~~~~~~~~~~~~~~~~~~~
...
Now add the new texts to our i18n.properties file and you’re done.
You can test the new features by navigating to the details page of any given product. After that, just create a new
comment for that product and post it.
Summary
You have learned how to use SAP Web IDE to create a simple worklist app from a template and you know where to
find the code in the Samples. Based on the initial app you have seen how easy it can be to generate or download
initial code and to extend it according to your own requirements. This tutorial also illustrated how to communicate
easily with an OData back end using the OData V2 model. Furthermore, it illustrated how to use the mock server
with both generated mock data and more realistic data.
Demo Apps
With the Demo Kit, we deliver some demo apps that show you how you can use the various features and controls
of OpenUI5.
You can try and download the apps at Demo Apps.
We have the following categories of demo apps:
● Showcase apps that show you how to use specific controls or features
● Tools that you can use to find a specific icon or theme parameter, for example, and can also be used as
templates for a similar app
● Apps that are created with our tutorials (see Get Started: Setup and Tutorials [page 38])
● Template apps (see App Templates: Kick Start Your App Development [page 1011])
The following tables give an overview of what each demo app shows.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
465
Features, Layouts, and Specific Topics
Demo App
Feature
Layouts
Shopping Cart
XML View [page 653]
sap.ui.layout.BlockL Filtering, custom filter
Busy Indicators [page 1198]
Specific Topic
ayout
Sorting
Device Adaptation [page 1046] sap.ui.layout.Vertic
Expression Binding [page
719]
alLayout
Formatting
Behavior-driven Development
sap.ui.layout.form.S with Gherkin [page 905]
impleForm
Mock Server [page 891]
Local storage
Browse Orders
XML View [page 653]
Busy Indicators [page 1198]
sap.ui.layout.Respon Sorting
siveGridLayout
Grouping
Device Adaptation [page 1046] sap.ui.layout.form.S
Expression Binding [page
impleForm
Formatting
Mock Server [page 891]
719]
Shop Administration Tool
XML View [page 653]
Busy Indicators [page 1198]
sap.ui.layout.BlockL Formatting
ayout
Device Adaptation [page 1046] sap.ui.layout.Respon
Expression Binding [page
719]
Custom Controls [page 1138]
Icon Explorer
XML Fragments [page 734]
XML View [page 653]
Busy Indicators [page 1198]
Device Adaptation [page 1046]
Expression Binding [page
719]
siveGridLayout
sap.uxap.ObjectPageL
ayout
sap.ui.layout.FixFle Filtering
x
Sorting
sap.ui.layout.Respon Formatting
siveSplitter
Mock server from http://
sap.ui.layout.Vertic sinonjs.org/
alLayout
Local storage
Custom model for icons
Theme Parameter Toolbox
XML Fragments [page 734]
XML View [page 653]
Busy Indicators [page 1198]
Custom Controls [page 1138]
466
sap.ui.layout.Respon Filtering
siveSplitter
Sorting
sap.ui.layout.form.S Formatting
impleForm
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Demo App
Feature
Layouts
Employee Directory
XML View [page 653]
sap.ui.layout.form.S Mock Server [page 891]
impleForm
Specific Topic
Routing and Navigation [page
797]
Hello World
JS View [page 665]
Bulletin Board
XML View [page 653]
Busy Indicators [page 1198]
sap.ui.layout.form.S Sorting
impleForm
Formatting
Mock Server [page 891]
Custom type
Manage Products
XML View [page 653]
Busy Indicators [page 1198]
Sorting
Formatting
Mock Server [page 891]
Worklist Template
XML View [page 653]
Filtering
Busy Indicators [page 1198]
Formatting
Mock Server [page 891]
Sorting
Master-Detail Template
XML Fragments [page 734]
Formatting
XML View [page 653]
List selector
Busy Indicators [page 1198]
Mock Server [page 891]
Device Adaptation [page 1046]
Sorting
Expression Binding [page
719]
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
467
Controls
Demo App
sap.m
Shopping Cart
Carousel
sap.m.semantic
Other Libraries
ColumnListItem
DatePicker
FormattedText
LightBox
List
MessagePage
MessagePopover
NavContainer
NotificationListItem
ObjectListItem
PullToRefresh
RangeSlider
SearchField
SegmentedButton
StandardListItem
Toolbar
Wizard
Browse Orders
IconTabBar
DetailPage
List
GroupSelect
ObjectHeader
MasterPage
PullToRefresh
SendEmailAction
SearchField
SegmentedButton
SplitApp
Table
468
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Demo App
sap.m
Shop Administration Tool
App
sap.m.semantic
Other Libraries
sap.tnt.NavigationLi
stItem
ColumnListItem
sap.tnt.ToolHeader
List
sap.tnt.ToolPage
ResponsivePopover
D3 charts (https://d3js.org)
SearchField
StandardListItem
Table
Toolbar
Icon Explorer
App
CustomHeaderContent
ColumnListItem
FullscreenPage
ComboBox
FormattedText
IconTabBar
MessagePage
OverflowToolbar
ScrollContainer
StandardListItem
Table
Tokenizer
Theme Parameter Toolbox
App
FullscreenPage
ColumnListItem
IconTabBar
OverflowToolbar
ScrollContainer
SearchField
Table
Toolbar
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
469
Demo App
sap.m
Employee Directory
App
sap.m.semantic
Other Libraries
IconTabBar
List
Toolbar
Hello World
App
Bulletin Board
App
FullscreenPage
ColumnListItem
SendEmailAction
IconTabBar
Toolbar
Manage Products
Worklist Template
App
FullscreenPage
Toolbar
SendEmailAction
App
FullscreenPage
ColumnListItem
SendEmailAction
MessagePage
SearchField
Table
Toolbar
470
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Demo App
sap.m
sap.m.semantic
Master-Detail Template
ColumnListItem
DetailPage
IconTabBar
FilterAction
List
GroupSelect
MessagePage
MasterPage
ObjectHeader
SendEmailAction
Page
SortSelect
Other Libraries
PullToRefresh
SearchField
SplitApp
Table
Toolbar
Vbox
ViewSettingsDialog
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
471
Essentials
This chapter and its sections describe the development concepts of OpenUI5, such as the Model View Controller,
data binding, and components. Use this section as a reference.
OpenUI5 Architecture
OpenUI5 is a client UI technology based on JavaScript, CSS and HTML5.
Apps developed with OpenUI5 run in a browser on any device (mobile, tablet or desktop PC).
When users access an OpenUI5 app, a request is sent to the respective server to load the application into the
browser. The view accesses the relevant libraries. Usually the model is also instantiated and business data is
fetched from the database.
472
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Depending on the environment in which OpenUI5 is used, the libraries or your applications can be stored, for
example, on an SAP NetWeaver Application Server or an SAP Cloud Platform, and business data can be accessed,
for example, using the OData model through a SAP Gateway.
Artifacts in the Framework
The top-level structural unit is called a library. Libraries are the master artifacts in the extensibility concept. They
bundle a set of controls and related types and make them consumable by Web applications. There are predefined
and standard libraries, like sap.m, with many commonly used controls. At the same time, it treats custom UI
libraries as first-class citizens, making it easy for you to write and use your own controls alongside the predefined
ones.
A UI element is the basic building block of our user interfaces; it is a reusable entity with properties, events,
methods, and relations. The most important relations are aggregations to other UI elements, and in this way a tree
structure of elements can be created.
From a developer's point of view, a control (e.g. Button, Label, TextField, or Table) is the most important
artifact. It is an object which controls the appearance and user interaction of a rectangular screen region. It is a
special kind of user interface element which can be used as the root of such a tree structure. In this way, it serves
as an entry point, especially for rendering. Besides controls, there are also other non-control elements, which
cannot be used as the root of such a tree structure, but only as a dependent part within it (e.g. TableRow,
TableCell).
Data types are first-class entities in the meta model. This allows reuse of types across libraries and extensibility of
the type system. The core library (technically, this is the sap.ui.core library) already defines a core set of types
that can be used in other libraries.
Bootstrapping: Loading and Initializing
To use OpenUI5 features in your HTML page, you have to load and initialize the SAPUI5 library.
You can use the OpenUI5 bootstrap script in your page to initialize OpenUI5 runtime automatically as soon as the
script is loaded and executed by the browser. For simple use cases as well as the default OpenUI5 installation, this
is sufficient to build and run UIs. In addition to this, you can specify the set of OpenUI5 libraries and the theme
used for your application in the configuration settings.
Note
If you run your app standalone, the bootstrap is added to your HTML page. In an SAP Fiori launchpad
environment, the launchpad executes the bootstrap and no additional HTML page is needed to display the app.
The following code snippet shows a typical bootstrap script tag:
<script id="sap-ui-bootstrap"
type="text/javascript"
src="resources/sap-ui-core.js"
data-sap-ui-theme="sap_belize"
data-sap-ui-libs="sap.m"
data-sap-ui-compatVersion="edge">
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
473
</script>
The attributes data-sap-ui-theme="sap_belize" and data-sap-ui-libs="sap.m" already provide
examples of how OpenUI5 runtime can be configured to the needs of an application.
Overview of Bootstrap Files
OpenUI5 provides several bootstrap files for different use cases. The following table gives an overview of the most
important resources and the respective use cases. The resource names refer to the resources/ folder in the
OpenUI5 installation. The actual base URL depends on your platform and administrative setup.
Resource
Description
sap-ui-core.js
This is the standard bootstrap file, which we recommend to
use for typical use cases. It already contains jQuery,
jquery-ui-position and only the minimum required
parts of the core library (sap.ui.core). Required files are
loaded dynamically using XMLHttpRequest (XHR).
For more information, see Standard Variant for Bootstrapping
[page 476].
Content Delivery Network (CDN)
You can access the libraries externally from a CDN. For more
information see Variant for Bootstrapping from Content
Delivery Network [page 476].
sap-ui-core-nojQuery.js
You use this bootstrap file for applications with their own
jQuery version. It also contains the minimum required parts of
the core library, but not jQuery and jquery-ui-
position.
For more information, see noJQuery Variant for
Bootstrapping [page 478].
sap-ui-core-all.js
This bootstrap file contains almost all resources of the
sap.ui.core library. Only a few duplicates, such as
multiple jQuery versions, testing resources, and so on, are
omitted. If you use this file for bootstrapping, the *-all.js
file is also loaded for all other libraries. This reduces the
number of JavaScript requests to the number of libraries
(typically 1..4).
Note
To ensure proper encapsulation, the *-all.js files will
be renamed in future versions of OpenUI5. The sap-ui-
core-all.js file remains as is, but for the files for other
libraries a name relative to the library will be used, for
example sap/m/library-all.js. Applications must
474
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Resource
Description
not address these files directly as OpenUI5 runtime loads
them. Only sap-ui-core-all.js can be directly
referenced in the bootstrap tag.
Note
This function is deprecated.
sap/ui/core/library-preload.js
This bootstrap file is similar to the sap-ui-core-all.js
file, but the modules are parsed and executed only on
demand, and not immediately.
Caution
An application must not reference this file. If the
configuration option is set to preload, OpenUI5
automatically loads the file.
For more information, see Preload Variant for Bootstrapping
[page 478].
sap-ui-core-lean.js
This bootstrap file is similar to the sap-ui-core.js file,
but in this use case only the jQuery and one OpenUI5 file are
loaded immediately and the other files are loaded
dynamically.
Caution
This use case is usually not used and may be removed in
future.
sap-ui-custom*.js
This bootstrap file is reserved for custom merged files
created by the application.
Note
The proposed naming scheme for these files needs to be
adapted in future versions for the same encapsulation
reasons as mentioned above.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
475
Standard Variant for Bootstrapping
Self-contained variant that includes the sap-ui-core.js file in the HTML page
The standard variant loads the SAPUI core modules, the jQuery core, as well as some jQuery plugins and the
application only has to specify the sap-ui-core.js file on its page. The following code snippet shows an
example:
<script
</script>
id="sap-ui-bootstrap"
src="resources/sap-ui-core.js"
data-sap-ui-libs="sap.ui.m"
data-sap-ui-theme="sap_belize" >
OpenUI5 synchronously loads the libraries specified in the data-sap-ui-libs configuration option. If a library
requires a library that is not specified in the configuration, OpenUI5 loads this library automatically. You use the
data-sap-ui-theme configuration option to specify the style sheet you want to load for all libraries.
After the bootstrap is finished in the attachInit callback, the application can call most of the OpenUI5 APIs. The
application can access the core APIs, or instantiate, configure, and place controls. The document object model
(DOM), however, can only be accessed after the controls have been rendered, that is, only after the document is
ready. The application can use the attachInitEvent method to be notified about that event.
Note
In the default configuration, OpenUI5 automatically activates the preload=sync mode when running from
optimized sources. For more information, see Preload Variant for Bootstrapping [page 478].
Related Information
Configuration of the OpenUI5 Runtime [page 481]
Variant for Bootstrapping from Content Delivery Network
OpenUI5 can either be loaded locally with a relative path from a Web server or externally from a Content Delivery
Network (CDN).
Note
Loading OpenUI5 from a CDN improves your app performance: You can load from a server that (in most cases)
is much closer to your location, and you can benefit from the caching mechanism and the language fallback
logic.
476
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Specific Version
Check the available versions with the respective maintenance status at https://openui5.hana.ondemand.com/
versionoverview.html.
Note
Only use a Stable version for productive apps. But if you want to also test a Preview version, we would really
appreciate getting your feedback!
You can refer to a specific version by using a versioned URL as in the example below:
<script id="sap-ui-bootstrap"
type="text/javascript"
src="https://openui5.hana.ondemand.com/1.42.6/resources/sap-ui-core.js"
data-sap-ui-theme="sap_belize"
data-sap-ui-libs="sap.m"></script>
The first segment of the URL after the host name is used to specify a concrete version.
Default Version
The default version of our libraries has the generic URL https://openui5.hana.ondemand.com/resources/sap-uicore.js (OpenUI5).
Caution
The default version is constantly being upgraded and this might have an impact on the stability of your
application. Use this version for testing purposes only.
If you want to use the default version, you can use the following bootstrap script:
<script id="sap-ui-bootstrap"
type="text/javascript"
src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-theme="sap_belize"
data-sap-ui-libs="sap.m"></script>
Cache Control
The cache control is different for dynamic and static resources. If you refer to the latest maintenance version
(dynamic), you have a maximum cache age of one week, if you refer to a specific (static) version, you have a
maximum cache age of 10 years. In both cases, cross-origin resource sharing (CORS) headers are set, so that you
can consume resources from the central location without any proxy in between.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
477
Related Information
Versioning of OpenUI5 [page 30]
noJQuery Variant for Bootstrapping
The noJQuery variant supports bootstrapping for an application that already integrates jQuery or uses a different
jQuery version than OpenUI5.
In this variant, you include the resources/sap-ui-core-noJQuery.js file in your HTML page. Make sure that
jQuery and jquery-ui-position have been loaded beforehand. The following code snippet shows an example:
<!-- include some jQuery version -->
<script src="my-jQuery-min.js" ></script>
<!-- application does not have its own jquery-ui-position, so it might use the
one from SAPUI5 -->
<script src="resources/sap/ui/thirdparty/jqueryui/jquery-ui-position.js" ></
script>
<!-- now booting SAPUI5 -->
<script
id="sap-ui-bootstrap"
src="resources/sap-ui-core-nojQuery.js"
data-sap-ui-libs="sap.m"
data-sap-ui-theme="sap_belize" >
</script>
Preload Variant for Bootstrapping
The preload variant for bootstrapping (library-preload.js) is used if you want to load all JavaScript modules
of a library in advance with one single request for performance reasons. It provides the auto, sync and async
parameters for synchronous or asynchronous loading of the modules.
Caution
An application must not reference this file. If the configuration option is set to preload, SAPUI5 automatically
loads the file.
OpenUI5 runtime executes the code only if the application requires the module. Thus, the preload variant
significantly reduces the number of roundtrips. To activate the preload variant, set the preload configuration
parameter with one of the following values:
● async (recommended)
If you set the preload configuration option to async, the runtime loads the modules for all declared libraries
asynchronously. Thus, any code that follows the OpenUI5 bootstrap tag can not be sure that the OpenUI5
classes are available already. Therefore, the application must delay the access to the OpenUI5 APIs by using
the Core.attachInitEvent method. OpenUI5 supports the async mode only for libraries that are loaded
by the OpenUI5 core. Libraries that are loaded dynamically by using the
sap.ui.getCore().loadLibrary() API will be preloaded with preload=sync.
478
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
● sync
If you set the preload configuration parameter to sync, the runtime loads the modules for all declared
libraries synchronously. After processing the bootstrap tag, all preload files of all libraries are loaded and the
libraries are initialized as usual. The preload=sync mode should be transparent for most applications.
● auto
The preload=auto configuration parameter is the default value. This mode checks whether OpenUI5 runtime
uses optimized sources. If optimized sources are used, it enables the preload=sync option to further
optimize the runtime. For normal or debug sources, the preload is deactivated.
Note
Preload sources always have to be optimized. Therefore, using the preload variant with the debug configuration
parameter is counterproductive and debug always overrides the preload configuration parameter.
You can easily check the preload variant with an existing application by specifying the sap-ui-preload=/mode/
parameter in the start URL by adding the data-sap-ui-preload attribute to the bootstrap tag:
<script
id="sap-ui-bootstrap"
src="resources/sap-ui-core.js"
data-sap-ui-libs="sap.m"
data-sap-ui-theme="sap_belize"
data-sap-ui-preload="async" >
</script>
Note
The preload=async option requires active cooperation of the application. It is therefore not activated
automatically, but only by configuration.
Note
You can combine the preload configuration option with other bootstrap variants such as sap-ui-corenoJQuery.
Related Information
noJQuery Variant for Bootstrapping [page 478]
Configuration of the OpenUI5 Runtime [page 481]
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
479
sap-ui5 Variant for Bootstrapping
The sap-ui5 variant loads all classes and controls of OpenUI5 standard libraries with one single request and is a
predecessor of the preload variant.
The sap-ui5 variant has the following constraints:
● You can not extend the set of libraries. For custom libraries you must use the all-in-one variant instead.
● All the contained code is executed. This may increase the initial startup time of the application depending on
the browser, the client computer or device, and so on.
● The file size is huge.
We recommend to use the preload variant instead. Keep in mind that the preload variant is used automatically for
optimized sources.
Initialization Process
The initialization process starts after OpenUI5 runtime is loaded.
The initialization of the OpenUI5 runtime comprises the following steps:
1. The jQuery plugins, which are mainly located in the jQuery.sap namespace, provide fundamental
functionality of OpenUI5, such as the modularization concept, a logging framework, performance
measurement, and so on.
2. The global object sap is defined.
3. The sap.ui.core.Core class is executed with all its dependencies.
4. The runtime configuration is determined from different sources.
5. All libraries and modules declared in the configuration as well as their dependencies are loaded.
6. For each loaded library, the CSS file of the configured theme is loaded.
7. When all libraries are loaded and the document is ready, the initEvent of the core is fired and all registered
handlers are executed.
Loading of Additional Resources During Bootstrap
The OpenUI5 runtime loads and interprets additional resources for the control libraries during bootstrap.
The files are loaded in the following sequence:
1. Library bootstrap file /<context-path>/resources/<library-name>/library.js
A JavaScript file that contains the JavaScript code for all enumeration types provided by the library as well as
library-specific initialization code that is independent from the controls in the library. The file calls the
sap.ui.getCore().initLibrary method with an object that describes the content of the library (list of
contained controls, elements etc.). For libraries that have been developed with OpenUI5 application
development tools or the OpenUI5 offline build tools, this file is generated automatically during the build
2. Library style sheet file /<context-path>/resources/<library-name>/themes/<theme-name>/
library.css
480
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
OpenUI5A standard CSS file that contains all styles relevant for this library. For application development tools,
this file is generated automatically during the build.
Dynamic Loading of Libraries
OpenUI5 provides the sap.ui.getCore().loadLibary() method to load libraries at runtime in addition to the
libraries declared in the runtime configuration.
After loading, you can use all controls from the library. For these additional libraries, the same restriction apply as
for the declared libraries: Accessing the document object model (DOM) is only possible after the
document.ready event of the HTML page. Also, rendering applies for these libraries in the same way as for the
declared libraries.
Configuration of the OpenUI5 Runtime
OpenUI5 provides several options for the configuration of the OpenUI5 runtime, such as runtime default values
and script tag attributes.
When the OpenUI5 bootstrap script is included in a page, the OpenUI5 runtime will automatically be initialized as
soon as the script is loaded and executed by the browser. For simple use cases and for a default OpenUI5
installation, this should already be sufficient to build and run UIs. The only additional information that usually is
specified, is the set of libraries and the theme that is used.
So a typical bootstrap script looks like this:
<script id="sap-ui-bootstrap"
type="text/javascript"
src="resources/sap-ui-core.js"
data-sap-ui-theme="sap_belize"
data-sap-ui-libs="sap.m"
data-sap-ui-compatVersion="edge">
</script>
For more information see Bootstrapping: Loading and Initializing [page 473].
You can use the following ways to provide configuration information.
Default Values
The easiest way to specify a configuration value is not to specify it. The OpenUI5 runtime contains a default value
for each configuration option. As long as you don't have to change the value, you don't specify it.
Individual Script Tag Attributes
For each configuration option, you can have one attribute in the bootstrap script tag.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
481
The attributes have to provide the following information:
● Attribute name
The attribute name is composed of the name of the configuration option and the data-sap-ui- prefix. The
first part of the prefix (data-) is necessary to comply with the W3C recommendations for custom attributes
in HTML. The second part (-sap-ui-) separates OpenUI5 attributes from custom attributes defined by any
other framework.
Note
Attribute names in HTML are case-insensitive and this also applies to the configuration attribute names.
However, OpenUI5 has defined some configuration options names in camel case, for example originInfo.
OpenUI5 converts these names automatically to lower case when accessing the configuration.
● Value
Element attributes in HTML have a string value by definition. For configuration options of type string, the
attribute value is equivalent to the value of the option.
Note
If the value contains specific HTML characters, such as '<' or '>', or if the value contains the same quote
character that is used to wrap the attribute value, the usual HTML escape mechanisms must be used: Use
entities for the specific HTML characters, for example &lt; instead of <, and switch the type of quotes from
single to double or vice versa.
For configuration options that are not of type string, the format of the allowed values has to be defined as
follows:
Type
Notation/Values
string
String; escaped according to the HTML conventions
boolean
true and x are both accepted as true values (caseinsensitive), all others are false. We recommend to use
false for false values
int
Any integer value
string array
Comma-separated list of values; comma is not supported
in the values (no escaping)
map from string to string
JavaScript object literal (preferably JSON syntax)
Single and Complex Configuration Attributes
The attribute data-sap-ui-config makes it possible to provide a single attribute with the configuration
information for the OpenUI5 runtime.
You can use this attribute instead of attaching individual options with individual configuration attributes to the
script tag. Its content is similar to the global configuration object, but without the enclosing parenthesis: It is a
comma separated list of key-value pairs.
482
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Note
The usual HTML escape mechanisms must be used if the value contains specific HTML characters (<, >, &) or
the quote character that is used to enclose the attribute value.
<script id="sap-ui-bootstrap"
type="text/javascript"
src="resources/sap-ui-core.js"
data-sap-ui-config="theme:'sap_belize',
libs:'sap.m'"
>
</script>
Global Configuration Objects
The global configuration object is a property in the global window object with property name sap-ui-config.
The property must be a simple object, where each property represents the configuration option of the
corresponding name.
To avoid conflicts with typical JavaScript coding, the name of the window property is not a valid JavaScript
identifier. The name structure is chosen to avoid conflicts with SAP objects. To define the object, quotes must be
used. If a configuration option has a name that is not a valid JavaScript identifier or that is a reserved token in
JavaScript, the property name in the configuration object must be quoted. Currently, such a configuration option
does not exist.
As the configuration is evaluated during bootstrap, the configuration object must be created before OpenUI5 is
bootstrapped. Otherwise, the contained configuration cannot be evaluated. As a consequence, using the global
configuration object requires another script tag in front of the bootstrap script tag. It is up to the application
whether it uses an inline script tag or a separate JavaScript file, which is loaded via a script tag, for this purpose. If
you use a dedicated file, it may require more work initially, but offers the following advantages:
● Several pages can share the file and, thus, use the same configuration.
● The Content Security Policy (CSP) mechanism as introduced, for example, by Firefox 4.0 and others requires
the use of a file.
The following code snippet shows an example for an inline script tag:
<script type="text/javascript">
window["sap-ui-config"] = {
theme : "sap_belize",
libs : "sap.m",
};
</script>
<script id="sap-ui-bootstrap"
src="resources/sap-ui-core.js"
>
</script>
This option requires an additional script or script tag, but it offers the following advantages:
● Possibility to share configuration between pages
● Can be used in environments where the scrip tag cannot be influenced, for example, because it is created out
of some configuration, like in some mashup frameworks
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
483
● Allows to provide configuration before the core boots
URL Parameters
Configuration parameters can be added to the URL of an app.
The URL parameter name is composed of the name of the configuration option and the sap-ui- prefix, for
example like index.html?sap-ui-debug=true&sap-ui-preload=off.
Note
The W3C proposed that the data- prefix is not needed and not even allowed here as all URL parameters are
kind of custom parameters.
The value of a URL parameter is of type string and the same type mapping as for HTML attributes applies.
However, URLs require a different encoding than HTML; they use, for example % encoding instead of entity
encoding.
For security reasons, only some configuration options can be set via URL parameters. An application can set the
ignoreUrlParameters option to true to disable URL configuration parameters completely.
Runtime Configuration Object
The runtime configuration object enables you to modify a limited set of configuration options at runtime.
The configuration options above are evaluated during the OpenUI5 runtime boots. After that, all changes to these
parameters are ignored. To read the final configuration result, you can use the
sap.ui.getCore().getConfiguration() method.
The same object also provides set methods for a very limited set of configuration options that can be modified at
runtime. The runtime and/or the controls can react on these configuration changes. The most prominent (and so
far only) example for such a configuration option is the theme.
Order of Significance
1. Attributes of the DOM reference override the system defaults.
2. URL parameters override the DOM attributes; empty URL parameters reset the parameter to its system
default.
3. If you call setters at runtime, any previous settings calculated during object creation are overwritten with the
new value.
484
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Configuration Options and URL Parameters
The complete list of configuration options available in OpenUI5 can be found in the API Reference under
sap.ui.core.Configuration. The following table shows a subset of the available configuration options.
Note
A subset of these configuration parameters can also be used as URL parameter ("URL: Yes"). The URL
parameter name is composed of the name of the configuration option and the sap-ui- prefix, for example like
sap-ui-debug=true.
Option
accessibility
Type
Type: boolean
Default value: true
URL: Yes
Modifiable at runtime: No
If set to true, the OpenUI5 controls are rendered for or
running in accessibility mode.
animationMode
Type: string
Default value: full
URL: Yes
Modifiable at runtime: Yes
The following animation modes are available:
●
full: all animations are shown
●
basic: a reduced, more light-weight set of animations
●
minimal: no animations are shown, except animations
of fundamental functionality
●
none: deactivates the animation completely
This parameter replaces the deprecated Boolean
animation parameter.
For all controls that implement the animation parameter,
the animationMode is set as follows:
●
If animation is set to true, this is interpreted as
animationMode full
●
If animation is set to false, this is interpreted as
animationMode minimal
appCacheBuster
Type: true | string[]
Default value: []
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
485
Option
Type
URL: Yes
Modifiable at runtime: Yes, with AppCacheBuster API (see
Application Cache Buster: Enhanced Concept [page 826])
If set to a non empty list of URLs, the AppCacheBuster will
be activated and will load component version info files from
the configured set of URLs (see Application Cache Buster
[page 824]).
areas
Type: string[]
Default value: null
URL: No
This configuration parameter defines UI areas that shall be
created in advance; use to create new UI areas and
sap.ui.getCore().getUIArea(id).destroy() to
delete existing UI areas at runtime.
autoAriaBodyRole
Type: boolean
Default value: true
URL: No
Modifiable at runtime: No
Determines whether the framework automatically adds the
ARIA role application to the HTML body.
bindingSyntax
Type: string
Default value:
●
As of OpenUI5 1.28, in combination with data-sap-
ui-compatVersion="edge": complex
●
default
The meaning of the default value 'default' depends
on the compatibility version
sapCoreBindingSyntax. If the compatibility version
is at least 1.28, the 'complex' binding syntax is assumed,
otherwise the 'simple' binding type. In other words:
applications that configured a general compatibility
version of 1.28 (or higher or 'edge'), will automatically run
with the 'complex' binding syntax.
URL: No
Modifiable at runtime: No
486
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Option
Type
This configuration parameter defines whether the simple or
the complex binding syntax is used. The parameter only
affects bindings that are defined as strings, for example in the
constructor of a control, or when specifying a binding in a
declarative view, such as XML view or HTML view.
calendarType
Type: gregorian | islamic | japanese |
persian (case-sentitive)
Default value: If there is no value defined, the actual value is
determined from the locale data for the configured locale.
URL: Yes
Modifiable at runtime: See API Reference:
sap.ui.core.Configuration.setCalendarType.
Defines the calendar type that is used for locale-dependent,
date-related features (for example, formatting or parsing date
and time).
debug
Type: boolean or string
Default value: false
URL: Yes
Modifiable at runtime: No
If set to true, the debug sources are loaded; if the bootstrap
code is loaded from an optimized source, the bootstrap will
be aborted and start anew from a debug source.
You can also specify a comma-separated list as string that
contains all modules that should be loaded as debug source.
Example: index.html?sap-ui-debug=sap/ui/
model/odata/v2/ will load all debug sources for all
modules of the OData V2 model. All others modules will be
taken from the preload (if preload is active).
You can use the following patterns:
●
A trailing slash (/) means that the complete package
should be included (shortcut for /**/*)
Example: sap/ui/model/odata/v2/ loads
everything from the sap/ui/model/odata/v2/
package as debug source (also nested packages
sap/ui/model/odata/v2/**/*).
●
**/ matches any package or sequence of packages
Example: **/v2/ loads any package named v2 as
debug sources like odata/v2, json/v2/ etc.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
487
Option
Type
●
* matches any part of a simple name
Example: sap/ui/model/* matches all files directly
contained in the model package, but not in nested
packages (for example, not v2 or v4)
Note
You can also select the debug sources in the technical
information dialog. For more information, see Technical
Information Dialog [page 932].
formatLocale
Type: string
Default value: undefined
URL: Yes
Modifiable at runtime: No
This configuration parameter defines the locale used for
formatting purposes; the default values for the locale are
derived from the language.
frameOptions
Type: string
Default value: default
URL: No
Modifiable at runtime: No
Frame options mode; for more information, see Frame
Options [page 1074]
frameOptionsConfig
Type: object
Default value: undefined
URL: No
Modifiable at runtime: No
Advanced frame options Configuration; for more information,
see Frame Options [page 1074]
ignoreUrlParams
Type: boolean
Default value: false
URL: No
Modifiable at runtime: No
Security-relevant parameter that allows applications to
disable configuration modifications via URL parameters.
488
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Option
inspect
Type
Type: boolean
Default value: false
URL: Yes
Modifiable at runtime: No
If set to true, the sap-ui-debug.js module is included
and provides some supportability features
language
Type: string
Default value: user language
URL: Yes
Modifiable at runtime: yes, with restrictions.
This configuration parameter defines the language that shall
be used for localized texts, formatting, and so on. For more
information, see API Reference:
sap.ui.core.Configuration.setLanguage and
Identifying the Language Code / Locale [page 846].
libs
Type: string[]
Default value: [ ]
URL: No
Modifiable at runtime: Yes
This configuration parameter defines a list of libraries that
shall be loaded initially; use the loadLibrary() method to
load further libraries.
For more information, see: loadLibrary
logLevel
Type: 0|1|2|3|4|5|6|NONE|FATAL|ERROR|
WARNING|INFO|DEBUG|ALL
Default value: ERROR
URL: Yes
Modifiable at runtime: Yes
This configuration parameter sets the log level to the given
value; for minified (productive) sources, the default level is
ERROR, for debug sources it is DEBUG. At runtime, you can
modify the log level by using the
jQuery.sap.log.setLevel method.
manifestFirst
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Type: boolean
489
Option
Type
Default value: false
URL: Yes
Modifiable at runtime: Yes, by using option with same name in
the sap.ui.component API
If set to true, the descriptor for a component is read and
evaluated first, before loading the component code
(Component.js).
modules
Type: string[]
Default value: [ ]
URL: No
This configuration parameter defines a list of JavaScript
modules that shall be loaded after the core has been
initialized.
noConflict
Type: boolean
Default value: false
URL: No
Modifiable at runtime: No
If set to true, OpenUI5 forces jQuery into noConflict
mode.
noDuplicateIds
Type: boolean
Default value: true
URL: Yes
Modifiable at runtime: No
If set to true, this configuration parameter enforces that the
same IDs are not used for multiple controls; we highly
recommend this check as duplicate IDs may cause
unforeseeable issues and side effects.
onInit
Type: code
Default value: undefined
URL: No
Modifiable at runtime: No
This configuration setting defines code that has to be
executed after the initialization. The use of this parameter
490
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Option
Type
with string value (code) is no longer recommended as it
requires eval and therefore might conflict with stronger
content security policies. Either use it only in the form
window["sap-ui-config"].onInit =
function() { ... } or use the runtime API
sap.ui.getCore().attachInit() instead.
originInfo
Type: boolean
Default value: false
URL: Yes
Modifiable at runtime: No
If set to true, additional information for text resources is
provided that allows to determine the origin of a translated
text on the UI
preload
Type: not specified, auto, sync, or async
Default value: auto
URL: No
Modifiable at runtime: No
Between loading OpenUI5 core runtime and further libraries,
the so-called preload files are loaded. They contain all
modules of a library. The modules are only loaded, but not
executed, thus
The values are used as follows:
●
When set to auto, OpenUI5 runtime automatically uses
sync when running from optimized sources.
●
When set to sync, the preload files for the declared
libraries are loaded synchronously.
●
When set to async (recommended), the preload files
are loaded asynchronously.
●
preloadLibCss
For any other value (for example blank), the preload
feature is deactivated and modules are loaded on
demand.
Type: string[]
Default value: []
URL: Yes
Modifiable at runtime: No
This configuration setting specifies a list of UI libraries using
the same syntax as the libs property, for which the
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
491
Option
Type
OpenUI5 core does not include the library.css
stylesheet in the head of the page. If the list starts with an
exclamation mark (!), no stylesheet is loaded at all for the
specified libs. In this case, it is assumed that the application
takes care of loading CSS, for example, a manually merged,
single CSS file. Otherwise, the Core instructs the backend to
create a merged CSS for the specified libs. In both cases, if
the first libraries name is an asterisk (*), it will be expanded to
the list of already configured libraries.
Note
The merge feature is currently only available for Java and
only for apps that include the additional backend
component resource-ext. Without the merge,
applications can include their own merged CSS file and
suppress the loading of the standard library.css.
resourceRoots
Type: object
Default value: undefined
URL: No
Modifiable at runtime: string can be used to
define a location for all resources.
jQuery.sap.registerModulePath(),
sap.ui.localResources()
To provide a URL location that is not overwritten by a
component later on, final can be set to true, for example:
{'url' : '/that/is/the/prefix/', 'final' :
true}
For more information, see jQuery.sap.registerModulePath()
rtl
Type: boolean
Default value: false
URL: Yes
Modifiable at runtime: Yes, with restrictions. For more
information, see API Reference:
sap.ui.core.Configuration.setLanguage and
API Reference:
sap.ui.core.Configuration.setRTL.
If set to true, all controls are rendered in right-to-left (RTL)
mode; not yet determined automatically.
492
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Option
statistics
Type
Type: boolean
Default value: false
URL: Yes
Modifiable at runtime: No
Activates end-to-end traces and measurement of response
times For more information, see Interaction Tracking for
Performance Measurement [page 981]
theme
Type: string
Default value: base
URL: Yes
Modifiable at runtime: Yes
This configuration parameter defines the theme that shall be
used for the current page; you can change the theme at
runtime by calling sap.ui.getCore().applyTheme().
Theme Root:
When the theme string contains an at-sign (@), anything
before the @ is assumed to denote the ID of the theme while
anything after the @ is assumed to represent the URL location
of the theme. To defend against XSS attacks, only tthe serverrelative part of the URL is used, any host or port prefix will be
ignored.
themeRoots
Type: object
Default value: undefined
URL: No
Modifiable at runtime:
sap.ui.getCore().setThemeRoot()
This configuration parameter defines the location of themes.
trace
Type: boolean
Default value: false
URL: No
Modifiable at runtime: No
If set to true, this configuration parameter activates an
overlay div that contains a trace.
uidPrefix
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Type: string
493
Option
Type
Default value: '--'
URL: No
Modifiable at runtime: No
Prefix to be used for automatically generated control IDs;
must be choosen carefully to avoid conflicts with IDs defined
by the application or DOM IDs.
versionedLibCss
Type: boolean
Default value: false
URL: Yes
Modifiable at runtime: No
If set to true, the version parameters are included in
requests to the library theme resource (for example, the
parameter library.css?version=1.0.1&sap-ui-
dist-version=1.0.2 is added. version contains the
library version and sap-ui-dist-version the version of
the OpenUI5 distribution .
This applies to the following resources:
●
library(-RTL).css (or any other variation)
●
library-parameters.json
URLs within the CSS or parameters are not modified.
weinreId
Type: string
Default value:
URL: Yes
Modifiable at runtime: No
weinreServer
Type: string
Default value:
URL: No
Modifiable at runtime: No
URL to a WEINRE server to be used for debugging purposes;
if set, OpenUI5 automatically includes the WEINRE target
modules.
whitelistService
Type: string
Default value:
URL: No
494
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Option
Type
Modifiable at runtime: No
URL to a whitelist service; see Whitelist Service [page 1074]
Experimental Options
The options listed in the table below are 'experimental'. They may be removed in future versions, or their definition
or behavior may change in an incompatible way. Experimental options are identified by the name prefix xx-.
Experimental configuration options are used for support scenarios where OpenUI5 development needs the
freedom to evolve supportability features over time. Others are related to experimental features where the
underlying feature still may change. When an experimental configuration option becomes mature, the xx- prefix is
removed from the definition. For compatibility reasons, the old name with the xx- prefix will still be supported.
Option
xxcomponent
Preload
Type
Note
This is an experimental feature and may be modified or removed in future versions.
Type: sync | async |off
Default value: same as preload
URL: Yes
Modifiable at runtime: No
Allows to suppress the preload of component resources (Component-preload.js). By default, the
component resources are automatically preloaded when preloads are active in general (e.g. when running
against the optimized OpenUI5 runtime and not running in debug mode). With this parameter, the preload
can be switched off without affecting the library preload. sync or async has no meaning, both are accepted
to be compatible with the library preload, but the code that creates a component decides whether this
happens synchronously or asynchronously. .
xxdebugModu
leLoading
Note
This is an experimental feature and may be modified or removed in future versions.
Type: boolean
Default value: false
URL: Yes
Modifiable at runtime: No
When set to true, the SAPUI5 module loading feature produces DEBUG output for every required, executed,
or required but already loaded module. This can help to analyse issues with dependency order, and so on.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
495
Option
xxdebugRend
ering
Type
Note
This is an experimental feature and may be modified or removed in future versions.
Type: boolean
Default value: false
URL: Yes
Modifiable at runtime: No
When set to true, some components of the OpenUI5 rendering system (RenderManager, UIArea)
create a far more verbose debug output for rendering steps, for example:
●
Which controls have to be rendered?
●
Who invalidated the control? (stacktrace)
●
Was one rendering run sufficient, or have there been multiple runs?
xx-e2etrace
Note
This is an experimental feature and may be modified or removed in future versions.
Type: boolean
Default value: false
URL: No
Modifiable at runtime: No
This configuration setting enables the end-to-end trace capability.
xx-fakeOS
Note
This is an experimental feature and may be modified or removed in future versions.
Type: string
Default value: undefined
URL: Yes
Modifiable at runtime: No
You use this configuration parameter to simulate iOS, Android and BlackBerry on desktop PCs for easier
development of mobile apps and controls. The following values are supported:
●
ios
●
android
●
blackberry
The parameter modifies the user-agent and runtime and theming act as the selected mobile platform. This
includes browser detection mechanisms such as jQuery.browser. This configuration parameter is not
496
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Option
Type
handled by the OpenUI5 configuration object and is only available as URL parameter and as attribute in the
bootstrap script tag.
Note
This configuration parameter only works on desktop WebKit browsers, such as Chrome and Safari and
Internet Explorer 10. On other browsers and on mobile devices the configuration parameter has no effect.
Internet Explorer 10 only supports the Windows phone simulation and the Webkit browsers only support
iOS, Android, and Blackberry simulation.
xxlibraryPr
eloadFile
s
Note
This is an experimental feature and may be modified or removed in future versions.
Type: string[]
Default value: both
URL: Yes
Modifiable at runtime: No
Allows to enforce the use of a specific preload file type:
●
●
for all libraries: ?sap-ui-xx-libraryPreloadFiles=json
for individual libraries (might be a comma separated list): ?sap-ui-xx-
libraryPreloadFiles=sap.m:none,sap.ui.layout:json
●
for a combination of both: ?sap-ui-xx-
libraryPreloadFiles=both,sap.m:none,sap.ui.layout:js
Possible values for the file types are
●
none (no preload at all)
●
json (only try to load library-preload.json)
●
js (only try to load library-preload.js)
●
both (first try js, then json).
Any other value will be ignored. The default is both for all libraries.
xxloadAllMo
de
Note
This is an experimental feature and may be modified or removed in future versions.
Type: boolean
Default value: false
URL: No
Modifiable at runtime: No
This configuration parameter is used internally by OpenUI5 runtime. To activate it, load sap-ui-core-
all.js instead of sap.ui.core.js.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
497
Option
xx-noless
Type
Note
This is an experimental feature and may be modified or removed in future versions.
Type: boolean
Default value: false
URL: Yes (only!)
Modifiable at runtime: No
Only useful at development time: when set to true, the browser-based compilation of LESS theming files is
suppressed, only the CSS that is created at built-time will be loaded.
xx-nosync
Note
This is an experimental feature and may be modified or removed in future versions.
Type: boolean | warn
Default value: false
URL: Yes
Modifiable at runtime: No
When set to warn, any use of synchronous XHRs will be reported with a warning in the console. When set to
true, such calls will cause an error.
xxshowLoadE
rrors
Note
This is an experimental feature and may be modified or removed in future versions.
When executing a loaded module synchronously, some browsers do not provide a proper error location. By
setting this configuration parameter to true, OpenUI5 can be advised to load a failed script a second time,
but asynchronously with a script tag. This usually results in an easier to understand syntax error message and
a code location.
xxsupported
Languages
Note
This is an experimental feature and may be modified or removed in future versions.
Type: string[]
Default value: []
URL: Yes
Modifiable at runtime: No
With this option the client can be instructed to limit its backend requests for translatable texts to the
configured set of languages. An empty value or the value * allows any language, the value default limits
the requests to the set of languages that are delivered with OpenUI5.
498
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Option
Type
xx-testmobile
Note
This is an experimental feature and may be modified or removed in future versions.
Type: boolean
Default value: false
Modifiable at runtime: No
This configuration parameter activates support for mobile device-specific events, such as touch events. This
enables you to test standard OpenUI5 controls on mobile devices.
xxviewCache
Note
This is an experimental feature and may be modified or removed in future versions.
Type: boolean
Default value: true
URL: Yes
Modifiable at runtime: No
Allows to disable the view caching, for example, during development. (See XML View Cache [page 662].)
xxwaitForTh
eme
Note
This is an experimental feature and may be modified or removed in future versions.
Type: boolean
Default value: false
URL: Yes
Modifiable at runtime: No
If set to true, the first (initial) rendering of the application will be delayed until the theme has been loaded
and applied (until Core.isThemeApplied()). Helps to avoid FOUC (flash of unstyled content).
Compatibility Version Information
Compatibility version flags allow applications to react to incompatible changes in OpenUI5.
Caution
The concept of compatibility versions has been abandoned as of version 1.28. Therefore, there will be no new
compatibility version flags in the future. If you start building a new application please set data-sap-uicompatVersion="edge" on your OpenUI5 bootstrap tag.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
499
As described in the compatibility rules, changes to OpenUI5 features are compatible, see Compatibility Rules
[page 18]. In some cases, however, it may make sense to change the behavior of a feature, for example, to change
the default values or to use an optimized implementation and these changes may lead to incompatibilities.
Note
We recommend to adapt to new feature versions as soon as possible.
The compatibility version configuration works as follows:
● A version flag is introduced if a feature change is incompatible.
● The version flag has to be defined in the OpenUI5 bootstrap tag either globally (data-sap-uicompatVersion or individually for each feature (for example data-sap-ui-compatVersion-xyz).
Example with compatVersion "1.18"
<script id="sap-ui-bootstrap"
type="text/javascript"
src="resources/sap-ui-core.js"
data-sap-ui-theme="sap_belize"
data-sap-ui-libs="sap.m"
data-sap-ui-compatVersion="1.18"
data-sap-ui-compatVersion-xyz="1.16"
>
</script>
● If no version is defined, the default behavior of the feature applies.
● If an explicit version is specified, the behavior of the specified version is applied.
● If a version edge is specified, the newest behavior of the feature is applied.
● A fallback mechanism is implemented. The following table is an example of possible configuration options for
feature "xyz":
data-sap-ui-compatVersion
data-sap-ui-compatVersion-
Default feature xyz
xyz
Resulting compatibility
version
--
--
1.14
1.14
1.16
--
1.14
1.16
--
1.16
1.14
1.16
1.18
1.16
1.14
1.16
edge
..
1.14
1.18
OpenUI5 supports the following compatibility version flags:
Flag
data-sap-ui-compatVersion-flexBoxPolyfill
Description
The flexBoxPolyfill for Internet Explorer 9 was
deprecated in 1.16 due to functional deficiencies. When the
compatibility version is 1.16 or higher, the polyfill is not active
at all. Otherwise, the buggy implementation behaves as
before, so that it still works in existing applications.
Default value: 1.14
500
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Flag
Description
data-sap-ui-compatVersionsapMeTabContainer
The TabContainer was deprecated in 1.15. When the
compatibility version is 1.16 or higher, an error is logged to the
console indicating that sap.m.IconTabBar should be
used instead.
Default value: 1.14
data-sap-ui-compatVersionsapMeProgessIndicator
--
data-sap-ui-compatVersion-sapMGrowingList
--
data-sap-ui-compatVersion-sapMListAsTable
--
data-sap-ui-compatVersionsapMDialogWithPadding
By default, the content area of Dialog had paddings. To
make the padding consistent with other popups, the padding
is removed for compatibility versions 1.16 or higher. If the
padding is still needed inside the content area of Dialog,
add the CSS style class sapUiPopupWithPadding to
Dialog by calling the addStyleClass function.
Default value: 1.14
data-sap-ui-bindingSyntax
This configuration parameter defines whether the simple or
the complex binding syntax is used. The parameter only
affects bindings that are defined as strings, for example in the
constructor of a control, or when specifying a binding in a
declarative view, such as XML view or HTML view.
For versions lower than 1.28, the default value is default
which only has very limited features. As of version 1.28, the
default is complex.
Related Information
Compatibility Rules [page 18]
Structuring: Components and Descriptor
OpenUI5 provides faceless components for services that deliver data from the back end system, and UI
components that extend components and add rendering functionality. The descriptor provides a central, machine-
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
501
readable and easy-to-access location for storing metadata associated with an application, an application
component, or a library.
Components
Components are independent and reusable parts used in OpenUI5 applications.
An application can use components from different locations from where the application is running. Thus,
components can be developed by different development teams and be used in different projects. Components also
support the encapsulation of closely related parts of an application into a particular component. This makes the
structure of an application and its code easier to understand and to maintain.
Note
Constraints due to cross-origin issues also apply to components.
OpenUI5 provides the following two types of components:
● Faceless components (class: sap.ui.core.Component)
Faceless components do not have a user interface and are used, for example, for a service that delivers data
from a back-end system.
● UI components (class: sap.ui.core.UIComponent)
UI components extend components and add rendering functionality to the component. They represent a
screen area or element on the user interface, for example, a button or a shell, along with the respective
settings and metadata. sap.ui.core.UIComponent extends sap.ui.core.Component and adds
rendering functionality to the component.
The sap.ui.core.Component class is the base class for UI and faceless components and provides the metadata
for both types of components. To extend the functionality, components can inherit from their base class or from
another component.
Components are loaded and created via the component factory function sap.ui.component. You can either pass
the name of the component or the URL of the descriptor file (manifest.json) to load it via the descriptor, see
Manifest First Function [page 514]. We recommend loading the component using the descriptor (if available) - it
improves performance during the initial load since the loading process can be parallelized and optimized.
After loading the descriptor, the component factory can load the dependencies (OpenUI5 libraries and other
dependent components) in parallel next to the component preload, and also models can be preloaded.
Structure of a Component
A component is organized in a unique namespace, the namespace of the component equals the component name.
Basically, a component consists of the component controller (Component.js) and a descriptor
(manifest.json). Only the component controller is mandatory, but we recommend to also use the descriptor
file. The descriptor then contains the component metadata, and also expresses the component dependencies and
configuration (see Descriptor for Applications, Components, and Libraries [page 513]). All required and optional
resources of the component have to be organized in the namespace of the component.
502
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Note
Optional resources are, for example, CSS, js, or i18n files, views, controllers)
The following figure gives an example of a component folder structure.
The ComponentContainer control wraps a UI component. You use the ComponentContainer control in the
OpenUI5 control tree in the same way as any other control.
Differentiation to Other Concepts in OpenUI5
The following list explains how other concepts used in OpenUI5 are distinguished from the OpenUI5 components
concept:
● Composite controls
Both concepts provide a set of controls behind a single interface. Composite controls are intended for reuse
within control development and allow to include existing controls in a complex control whereas components
are intended for reuse in application development.
● UI library
The UI library is the deployable unit around controls: Controls are never deployed standalone, but as part of a
control library. Components, however, are self-contained and should not be used to deploy controls.
● Notepad control
A notepad control is another way to define a control. Notepad controls have all the characteristics of a control.
● MVC
The MVC concept allows to define views and controllers and, thus, to structure and reuse parts within an
application. As MVC can only be deployed separately and has no means to define dependent styles or scripts
that are loaded together with a view, this concept is of limited use across different applications.
● Application
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
503
Applications have a single start URL and contain everything belonging to the application in a deployable unit.
However, it is not possible to embed an application into another application as the knowledge about the
required libraries, scripts and additional style sheets is contained in the index.html.
Related Information
Descriptor for Applications, Components, and Libraries [page 513]
API Reference: sap.ui.core.Component
Component.js File
The Component.js file is the component controller and provides the runtime metadata and the component
methods.
A component controller is defined with the asynchronous module definition (AMD) syntax. In the sap.ui.define
statement; the required dependencies can be declared which can be used in the controller.
To create an OpenUI5 component, you extend either the Component or UIComponent base class and pass the
name of the module (namespace + .Component).
sap.ui.define(['jquery.sap.global', 'sap/ui/core/UIComponent'],
function(jQuery, UIComponent) {
"use strict";
var Component = UIComponent.extend("samples.components.sample.Component", {
metadata : {
manifest : "json"
}
});
return Component;
});
The metadata of the component controller should be used to declare the runtime metadata only (which are the
properties, aggregations, associations and events).
We recommend to define the component metadata externally in the descriptor (manifest.json), because the
descriptor for components is mandatory for modern components and allows performance optimizations.
Related Information
Using and Nesting Components [page 508]
504
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Component Metadata
The component class provides specific metadata for components by extending the ManagedObject class. The
UIComponent class provides additional metadata for the configuration of user interfaces or the navigation
between views.
Note
With the introduction of the descriptor for applications, components, and libraries, we recommend to migrate
the component metadata to the descriptor. The descriptor is inspired by W3C’s Web Application Manifest and
provides comprehensive information for applications, components and libraries. For more information, see
Descriptor for Applications, Components, and Libraries [page 513]. The metadata property manifest must
be set to json to indicate that the manifest.json file should be loaded and used:
sap.ui.core.Component.extend("some.sample.Component", {
"metadata": {
"manifest": "json"
}
});
You can also define the descriptor inline by just providing an object. However, we do not recommend this
because this would prevent that the descriptor can be analyzed by tools.
The metadata defined in Component.js is common for faceless components and UI components. The following
parameters are available:
● manifest: Specifies if your component uses the descriptor
● abstract: Specifies if your component class is an abstract class that serves as a base for other components
● version: Version of your component; this parameter belongs to the design time metadata and is currently
not used; it may be used in the future in the design time repository
● properties, aggregations, associations, and events: Define these for your component in the same
way as for a control. For more information, see Defining the Control Metadata [page 1141].
● library: Specifies the library to which your component belongs to
The following properties are deprecated and no longer needed if you use the descriptor:
● includes: Array of strings containing the paths to CSS and JavaScript resources for your component; will be
added to the header of the HTML page and loaded by the browser. The resources will be resolved relative to
the location of Component.js.
● dependencies: Used to specify all external dependencies, such as libraries or components. Like the includes
for resources that are added to the application’s HTML, the dependencies are loaded by OpenUI5 core before
the component is initialized. Everything that is referenced here can be used in your component code right from
the start. Specify here external dependences such as libraries or components, that will be loaded by OpenUI5
core in the initialization phase of your Component and can be used after it.
○ libs: Path to the libraries that should be loaded by OpenUI5 core to be used in your component
○ components: Full path to the components that should be loaded by OpenUI5 core to be used in your
component
○ ui5version: Minimum version of OpenUI5 that the component requires; it helps to be ensure that the
features of OpenUI5 runtime used in this component are available. As OpenUI5 currently does not enforce
the use of the correct version, it is only used for information purposes.
● config: Static configuration; specify the name-value pairs that you need in the component
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
505
● extensions: Extensions for components and views, see Extending Apps [page 1102]
○ sap.ui.viewExtensions: Used for providing custom view content in a specified extension point in the
standard application
○ sap.ui.viewModifications: Used for overriding control properties in the standard application
○ sap.ui.viewReplacements: Used for replacing a standard view with a custom view
○ sap.ui.controllerExtensions: Used for extending a controller in a delivered standard application
with a custom controller
○ sap.ui.controllerReplacements: Used for replacing a controller in a delivered standard application
with a custom controller
Example for metadata in Component.js:
sap.ui.core.Component.extend("some.sample.Component", {
"metadata": {
"manifest": "json", // Specifies that your Component class uses the
descriptor via the manifest.json file
"abstract": true, // Specifies if your Component class is an abstract one
that serves as a base for your other components
"library": "sap.ui.core", // Specifies the library the component belongs to
"version": "1.0", // Version of your Component
"properties": { // Defined for components in the same way as for a control
or view
"config": "any"
}
}
});
In addition to the common metadata for components, the UIComponent class provides the following metadata for
UI compnents:
● publicMethods: Definition of public methods for your component
● aggregations: Defines aggregations for your component
The following properties are deprecated and no longer needed if you use the descriptor:
● rootView: Can be the view name as string or the view configuration object
● routing: Provides the default values for all views
○ config: Default values for routing that are applied, if no setting is specified by a route
○ viewType: View type of the view that is created, for example XML, JS or HTML
○ viewPath: Prefix that is preceding the view
○ targetParent: ID of the view in which the targetControl is searched
○ targetControl: ID of the control that contains the views
○ targetAggregation: Name of the aggregation of the targetControl that contains views
○ clearTarget: Boolean; if set to true, the aggregation should be cleared before adding the View to it
○ routes: Contains the configuration objects
○ name: Mandatory parameter used for listening or navigating to the route
○ pattern: String that is matched against the hash. The {} means this segment of the URL is passed to
a handler with the value it contains
○ view: Name of the view that is created
Example for UI component metadata:
sap.ui.core.UIComponent.extend("some.sample.UIComponent", {
"metadata": {
506
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
}),
}
"publicMethods": [ "render" ],
"aggregations": {
"rootControl": {
"type": "sap.ui.core.Control", multiple: false, visibility: "hidden"
}
}
Properties Section in Component Metadata
You can add a properties section to the metadata for all properties that can adopt different values during runtime.
The getters and setters for these properties are generated automatically, but you can overwrite them if you require
additional functionality. The following example contains two properties at the end of the metadata section.
sap.ui.core.UIComponent.extend("samples.components.shell.Component", {
"metadata": {
"abstract": true,
"version": "1.0",
[… omitting some lines to make the example shorter]
"properties": {
"appTitle": {
"name":"appTitle",
"type":"string",
"defaultValue":"Default Value that will be replaced with something
meaningful through the setter for this property"
},
"someOtherProp": {
"name":"myProperty",
"type":"string",
"defaultValue":"Some text"
}
}
}
});
The getters and setters for these properties are generated automatically and can be overwritten if additional
functionality is required.
Methods Controlling the Initial Instantiation
OpenUI5 provides two methods for the initial instantiation of the component.
You can use the following methods:
● init
Overwrite this method for example to connect the model between the control and the component. This
method is not called by the application directly, but called automatically when you create the instance of the
component.
● createContent
For UI components, this method returns the component UI as a tree of SAPUI5 controls. Overwrite the method
in your component implementation. You use this method to place all the code which is required to fill your
component with the respective content, for example, creating an instance of the controls that should be used,
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
507
or connecting the view that should be displayed. For the latter, you simply need to set this view to the view you
want to use. See the following code snippet for an example:
this.view =
sap.ui.view({id:"myView",viewName:"samples.components.products.details.view.Detai
ls",type:sap.ui.core.mvc.ViewType.JS});
Note
The configuration properties for a component, that is, the settings given in the constructor or the
sap.ui.component call, are not available in the onInit and createContent methods. Use componentData
instead. For more information, see sap.ui.component.
You can also overwrite the getters and setters for component properties in the Component.js file.
Using and Nesting Components
You can use a ComponentContainer to wrap a UIComponent and reuse it anywhere within the OpenUI5 control
tree. You can use reuse components to nest components in other components.
Component Containers
To render UI components, you must wrap them in a ComponentContainer. You cannot use the placeAt method
to place UI components directly in a page. A component container carries specific settings and also contains the
lifecycle methods of a regular control, such as the onBeforeRendering and onAfterRendering methods. The
lifecycle methods of the ComponentContainer are forwarded to the corresponding methods of the nested
component.
The ComponentContainer separates the application and the nested component. The control tree and data
binding of the inner component are decoupled from the outer component.
If you want to share data with the inner component, you can use the propagateModel property on the
ComponentContainer to forward models and binding contexts to the inner component.
You load and create a UIComponent in one of the following ways:
● Load the component asynchronously before creating the container:
sap.ui.component({
name: "samples.components.sample",
async: true
}).then(function(oComponent) {
var oContainer = new sap.ui.core.ComponentContainer({
component: oComponent
});
oContainer.placeAt("target");
});
● Load the component asynchronously while creating the container:
var oContainer = new sap.ui.core.ComponentContainer({
508
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
name: "samples.components.sample",
lifecycle: sap.ui.core.ComponentLifecycle.Container,
async: true
});
oContainer.placeAt("target");
● Load the component asynchronously with "manifest first" mode by specifying the URL of the descriptor
(manifest.json):
sap.ui.component({
manifest: "samples/components/sample/manifest.json",
async: true
}).then(function(oComponent) {
var oContainer = new sap.ui.core.ComponentContainer({
component: oComponent
});
oContainer.placeAt("target");
});
● Load the component asynchronously with "manifest first" mode by specifying the component name:
sap.ui.component({
name: "samples.components.sample",
manifest: true,
async: true
}).then(function(oComponent) {
var oContainer = new sap.ui.core.ComponentContainer({
component: oComponent
});
oContainer.placeAt("target");
});
Note
You can use the lifecycle property to determine whether the container or your application code will take care
of destroying the component.
Using a Component Container to Load Components from a Different Location
You may want to load components from a location that is different from the location where the OpenUI5 libraries
are located or a location that is not registered as a resource root in the OpenUI5 bootstrap.
You can do so by defining the URL of the additional components as a setting for the component factory or the
component container:
● Loading the component asynchronously before creating the container:
sap.ui.component({
name: "samples.components.sample",
async: true,
url: "./"
}).then(function(oComponent) {
var oContainer = new sap.ui.core.ComponentContainer({
component: oComponent
});
oContainer.placeAt("target");
});
● Loading the component asynchronously when creating the container:
var oContainer = new sap.ui.core.ComponentContainer({
name: "samples.components.sample",
lifecycle: sap.ui.core.ComponentLifecycle.Container,
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
509
async: true,
url: "./"
});
oContainer.placeAt("target");
Here you use the lifecycle property to make sure that the component is destroyed when the container is
destroyed.
Reuse Components
To be able to reuse a component, the component has to be declared in the componentUsages section of the
manifest.json descriptor file as follows:
"sap.ui5": {
"componentUsages": {
"myreuse": {
"name": "sap.reuse.component",
"settings": {},
"componentData": {}
}
}
}
The reuse component is declared via its componentUsage ID as the key and the supported values are name (name
of the component), settings, and componentData. The values defined in the manifest.json file will be
merged with the values specified in the instance-specific component factory function. Allowed values in the
instance-specific factory function are settings, componentData, async, and id.
For more information, see Descriptor for Applications, Components, and Libraries [page 513].
If you want to exchange the reuse component, for example, to extend an app, you simple exchange the reuse
component in the manifest.json descriptor file.
Reuse components that are embedded by a library must have an explicit entry in the manifest.json in the
sap.app/embeddedBy section:
"sap.app": {
"embeddedBy": "../"
}
Under embeddedBy, you specify the relative path to the namespace root of the library. This ensures that tools like
the application index can discover embedded libraries and won't include them in the transitive scope (otherwise
you would get unwanted 404 requests). Additionally tools should declare a library dependency to the embedding
library. This will ensure that the library containing the component preload will be loaded automatically instead of
the trying to load the component preload by itself.
Instantiation
To instantiate the reuse component in the current component, you use an instance-specific factory function. The
factory function requires at least the componentUsage ID as a parameter (simplified usage) or a configuration
object that contains the usage and optionally settings and componentData (extended usage).
● Example for simplified usage:
var oComponentPromise = this.createComponent("myreuse");
510
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
● Example for extended usage:
var oComponentPromise = this.createComponent({
usage: "myreuse"
settings: {},
componentData: {},
async: true
});
Declarative Usage
You can also declare a reuse component directly, for example, in your JavaScript or XML code. In an XML view, the
local service factory can only be used via the ComponentContainer that has a superordinate component.
<View ...>
<ComponentContainer usage="myreuse" async="true"></ComponentContainer>
</View>
Migration
If you have been reusing components before we introduced the reuse feature described above, we recommend
that you refactor your code and implement the new logic.
If you use a component that is embedded in a library, and the application declares a dependency to that library,
remove the dependency to the library from the embedding application. Make sure that the application code does
not contain any direct references to the component or the embedding application.
Old Code
Recommended Code
manifest.json with dependency declaration only:
manifest.json with declaration of reuse components:
{
}
"sap.ui5": {
"dependencies": {
"components": {
"sap.reuse.component": {}
}
}
}
{
}
Component.js with nested reuse component:
"sap.ui5": {
"dependencies": {
"components": {
"sap.reuse.component": {}
}
},
"componentUsages": {
"reuse": {
"name": "sap.reuse.component"
}
}
}
Component.js that loads the reuse component
createContent: function() {
createContent: function() {
var oReuseComponent =
sap.ui.component({
"name": "sap.reuse.component"
});
var oReuseComponentPromise =
this.createComponent({ /* this =
Component instance */
"usage": "reuse"
});
}
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
}
511
Related Information
Working with Nested Components [page 807]
API Reference: sap.ui.core.ComponentContainer
API Reference: sap.ui.core.ComponentContainer.setLifecycle
Descriptor for Applications, Components, and Libraries [page 513]
Handling IDs in UI Components
Components are usually used with a root view and in this case, the component handles the prefixing of IDs of
views, elements, or controls, with the component ID.
This works similar to the prefixing of control IDs in XML views, see Support for Unique IDs [page 672]. However, if
you implement your own createContent function, you need to handle this yourself. The following two options
exist:
● Set the sapui5/autoPrefixId attribute in the manifest.json file to true. This is the easiest option.
● Use the createId function of the UI component to prefix the respective ID of a view, element, or control
yourself.
Use the byId function of the UI component to retrieve the views, controls, and elements that have been created in
a UI component.
Advanced Concepts for OpenUI5 Components
Advanced concepts for components include routing and navigation and component data as well as the event bus.
The following advanced concepts for components exist.
● Routing and navigation
UI components support the routing and navigation concept, see Initializing and Accessing a Routing Instance
[page 805].
● Extensibility and customizing
The extensibility and customizing concept allows you to extend and modify components in order to replace
and extend the views and controllers as well as to modify the views. A customization can be performed, for
example, on a custom application that extends a delivered standard application.
For more information, see Extending Apps [page 1102]
● Component data
The JSON object ComponentData contains any initial values of parameters that can be used in the
createComponent() method. Component data are already available for use in the createComponent()
method, but not the parameters. The parameters are available in the onBefore, the onAfterRendering and
the setter methods of the parameters.
Component data is provided from outside and can be configured as desired. Configuration data is static and
defined in the component. To change or extend the configuration, the component needs to be extended and a
new configuration has to be created and merged with the configuration in the parent component.
512
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
● Event bus of the component
The local event bus belongs to the component and can be used by all children of this component. Once a
component instance is destroyed, the listeners registered in the event bus are destroyed automatically. For
more information, see API Reference for the getEventBus method of sap.ui.core.Component.
Descriptor for Applications, Components, and Libraries
The descriptor for applications, components, and libraries is inspired by the Web Application Manifest concept
introduced by the W3C. The descriptor provides a central, machine-readable and easy-to-access location for
storing metadata associated with an application, an application component, or a library.
The data is stored in json format in the manifest.json file. The developer creates the file with attributes in
different namespaces. It contains, for example, the app ID, the version, the data sources used, along with the
required components and libraries. The existence of the manifest.json file must be declared in the component
metadata, which is then delivered as part of the application archive. After delivery, the file is read-only.
General Information
Every new version of OpenUI5 implies a new version of the app descriptor. In the following table you can see how
the OpenUI5 version is related to the descriptor version and the value of _version.
Table 9: AppDescriptor Release and OpenUI5 Version
AppDescriptor Release
OpenUI5 Version
_version
Version 2
>=1.30
1.1.0
Version 3
>=1.32
1.2.0
Version 4
>=1.34
1.3.0
Version 5
>=1.38
1.4.0
Version 6
>=1.42
1.5.0
Version 7
>=1.46
1.6.0
Version 8
>=1.48
1.7.0
Version 9
>=1.50
1.8.0
Version 10
>=1.52
1.9.0
For more information on the new fields introduced in each version, check out Migration Information for Upgrading
the Descriptor File [page 550]
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
513
Manifest First Function
The component factory function sap.ui.component enables you to load the manifest.json before the
component instance is created. With this, you can preload the dependencies (libraries and components) and, thus,
improve the performance for loading the component. The preload is also available for models, which can be
flagged for preload during component loading.
To enable this so-called “manifest first” function, you can choose one of the following options:
● Set the manifest flag to true.
// load via manifest option
sap.ui.component({
name: "sap.my.component",
manifest: true
});
● Specify a URL as parameter for manifest for the component factory function:
//load via manifest Url
sap.ui.component({
name: "sap.my.component",
manifest: "any/location/sap/my/component/manifest.json"
});
Note
When you enable manifest, all legacy component metadata needs to be migrated into the descriptor for
applications/components. Only those entries in the descriptor for components will be respected by the
component and all other entries will be ignored.
Descriptor Content
The content for the descriptor is contained in the following namespaces: without, sap.app, sap.ui, sap.ui5,
sap.platform.abap, sap.platform.hcp and sap.fiori. The following tables show the application-specific
attributes provided by the respective namespaces:
Table 10: Attributes in the without namespace
Attribute
Description
start_url
Start page of your app, if available
514
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Table 11: Attributes in the mandatory sap.app namespace
Attribute
id
Description
Mandatory attribute: Unique identifier of the app, which must
correspond to the component name
Note
The ID must not exceed 70 characters. It must be unique
and must correspond to the component name.
type
i18n
Possible values:
●
application
●
component
●
library
Relative URL to the properties file that contains the text sym­
bols for the descriptor; default: "i18n/
i18n.properties"
Note
The path to the i18n file must not exceed 100 characters.
applicationVersion
Mandatory attribute
embeds
Array of relative paths to the nested manifest.json files;
attribute is mandatory if a nested manifest.json exists
embeddedBy
Relative path back to the manifest.json file of an embed­
ding component or library; attribute is mandatory for a nested
manifest.json
title
Mandatory attribute: The entry is language-dependent and
specified via {{…}} syntax
subTitle
Language-dependent entry for a subtitle; specified via
{{...}} syntax
shortTitle
Short version of the title. Language-dependent entry has to be
specified via {{...}} syntax
info
Needed for CDM (Common Data Model) conversion of tiles.
Language-dependent entry has to be specified via {{...}}
syntax
description
Description; language-dependent entry that is specified via
{{…}} syntax
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
515
Attribute
Description
tags
Contains the following:
●
An array of language-dependent keywords that are
specified via {{…}} syntax, for example "keywords":
["{{keyWord1}}","{{keyWord2}}"].
●
An array of technicalAttributes (general techni­
cal attributes, for example, technical catalog, upper case
and language-independent attributes).
ach
Application component hierarchy (SAP's component names
for bug reports); attribute is mandatory for SAP apps, but is
not used so far for apps developed outside SAP
dataSources
Unique key/alias for specifying the used data sources; con­
tains the following information:
●
uri: Mandatory relative URL in the component; takes
embeddedBy into account, if filled, or the server abso­
lute of the data source, for example "/sap/opu/
odata/snce/PO_S_SRV;v=2/"
●
type: OData (default)|ODataAnnotation|
INA|XML|JSON
●
settings: Data source type-specific attributes (key,
value pairs), which are:
○
odataVersion: 2.0 (default), 4.0
○
localUri: Relative URL to local metadata docu­
ment or annotation uri
○
annotations: Array of annotations which referen­
ces an existing data source of type "ODataAnnota­
tion" under sap.app/dataSources
○
maxAge: Indicates the number of seconds the client
is willing to accept with regard to the age of the data
that is requested
cdsViews
Array of directly used CDS views
This attribute is optional and only added if used via INA proto­
col directly, not if used via OData service.
offline
Indicates whether the app is running offline; default is false
(online)
516
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Attribute
Description
sourceTemplate
If an app has been generated from a template, this attribute is
filled automatically by the generation tool (SAP Web IDE):
●
id: Mandatory ID of the template from which the app was
generated
●
version: Mandatory version of the template from which
the app was generated
openSourceComponents
Array of directly used open source libraries for documentation
purposes; not used when open source libraries are used via
SAPUI5 capsulation
●
name: Mandatory name of the open source component
●
version: Required if the open source component is part
of the app; not required if the open source component is
part of the SAPUI5 dist layer
●
packagedWithMySelf: Indicates if the open source
component is part of the app (true) or not (false)
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
517
Attribute
Description
crossNavigation
Cross-navigation for specifying inbounds and outbounds
●
scopes: Scope of a site
sapSite
●
inbounds: Unique key or alias to specify inbounds
(mandatory); contains:
○
semanticObject (mandatory)
○
action (mandatory)
○
icon: Used to overwrite sap.ui/icons/icon
○
title: Used to overwrite sap.app/title (lan­
guage-dependent entry to be specified via {{...}} syn­
tax)
○
subTitle: Used to overwrite sap.app/
subTitle (language-dependent entry to be speci­
fied via {{...}} syntax)
○
shortTitle: Used to overwrite sap.app/
shortTitle (language-dependent entry to be
specified via {{...}} syntax)
○
info: Language-dependent entry to be specified via
{{...}} syntax
○
displayMode: <ContentMode or
HeaderMode> Display mode for an inbound which
specifies what kind of tile is displayed. A static tile
can be displayed in content mode or header mode.
The tile in header mode is a text only tile without an
icon which allows longer title and subtitle.
○
hideLauncher (true/false): Indicates that an
inbound must not be represented as a tile/link
○
indicatorDataSource; specifies the data
source; contains:
○
dataSource: reference to sap.app/
dataSources (mandatory)
○
path: Relative path to sap.app/
dataSources uri (mandatory)
○
○
refresh: Defines the refresh interval
deviceTypes: Contains objects with device types
on which the app is running; if empty, use the default
from sap.ui/deviceTypes; the following device
types can be defined (true/false):
518
○
desktop
○
tablet
○
phone
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Attribute
Description
○
signature: Specifies the signature; contains:
○
parameters (mandatory): Contains parame­
ter names with the following information:
○
required (true/false)
○
filter: Represents the filter only if the in­
put parameter matches the filter; with a
mandatory value and format attribute
("plain", "regexp", "reference")
○
defaultValue: Specifies the default
value; has mandatory attributes value
(depending on the format this is a verbatim
default value) and format ("plain", "refer­
ence")
○
renameTo: Used for parameter mapping
to specify the parameter name in legacy
ABAP applications, for example,
RF05L_BUKRS for the CompanyCode
parameter
○
launcherValue: Defines an explicit
value that is used when a new file is created
from the descriptor
○
additionalParameters (mandatory): In­
dicates, how additional parameters to the de­
clared signature are handeled; values can be, for
example, "ignored", "notallowed", "allowed"
●
outbounds: Specifies outbounds with a unique key or
alias containing:
○
semanticObject (mandatory)
○
action (mandatory)
○
parameters: Specifies the parameter name
○
value: Represents a value to be used in the
outbound; with value (verbatim value for for­
mat "plain", or not supplied, or a binding refer­
ence for format "binding") and format (indi­
cates how value is to be interpreted, "plain",
"binding")
○
required: Indicator whether paramter is re­
quired (true, false)
○
additionalParameters: Indicates whether ad­
ditional context parameters are to be used:
○
ignored: Parameters are not used
○
allowed: Parameters are passed on to appli­
cation
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
519
Table 12: Attributes in the mandatory sap.ui namespace
Attribute
Description
technology
Specifies the UI technology; value is UI5
icons
Contains object with app-specific icons, which are:
●
icon: Icon of the app, can be chosen from Icon Explorer .
●
favIcon: ICO file to be used inside the browser and for
desktop shortcuts
deviceTypes
●
phone: 57x57 pixel version for non-retina iPhones
●
phone@2: 114x114 pixel version for retina iPhones
●
tablet: 72x72 pixel version for non-retina iPads
●
tablet@2: 144x144 pixel version for retina iPads
Mandatory; contains objects with device types on which the
app is running, such as:
●
desktop: Indicator for whether desktop devices are
supported, true (default), false
●
tablet: Indicator for whether tablet devices are sup­
ported, true (default),false
●
phone: Indicator for whether phone devices are sup­
ported, true (default),false
supportedThemes
Optional; array of supported SAP themes, such as sap_hcb,
sap_belize
fullWidth
Indicates whether an app shall run in full screen mode (true),
or not (false)
The sap.ui5 namespace is aligned with the previous component metadata and contributes the following
OpenUI5-specific attributes for the application descriptor, see Migrating from Component Metadata to Descriptor
[page 531] for more details.
Table 13: Attributes in the sap.ui5 namespace
Attribute
Description
resources
Relative URLs in the component, taking embeddedBy into
account if filled, pointing to js (JavaScript) and css resour­
ces that are needed by the app for specifying the mandatory
uri and an id (optional) for CSS. The JavaScript files are
loaded by the require mechanism. The CSS files are added
to the head of the HTML page as a link tag. The resources are
resolved relative to the location of the manifest.json file.
520
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Attribute
Description
dependencies
Mandatory; specifies the external dependencies that are
loaded by the OpenUI5 core during the initialization phase of
the component and used afterwards. These are the following
libraries or components:
●
minUI5Version: Mandatory; Minimum version of
OpenUI5 that your component requires; this information
ensures that the features of the OpenUI5 runtime version
of the component are available. As OpenUI5 does not cur­
rently enforce use of the correct version, the
minUI5Version is used for information purposes only.
If the minimum SAPUI5 version criteria is not fulfilled, a
warning is issued in the console log.
●
libs: ID (namespace) of the libraries that the OpenUI5
core should load for use in the component. If your app re­
quires a minimum version of the lib, specify the
minVersion for information purposes. Specify lazy
to indicate that the lib shall be lazy loaded.
●
components: ID (namespace) of the components that
the OpenUI5 core should load for use in your component.
If your app requires a minimum version of the compo­
nent, specify the minVersion for information pur­
poses. Specify lazy to indicate that the component shall
be lazy loaded.
For more information, see Descriptor Dependencies to Libra­
ries and Components [page 553].
componentUsages
Specifies the used components with the a unique key/alias.
Contains the following:
●
name: Mandatory name of the reuse component
●
settings: Settings of the component
●
componentData: Component data of the component
For more information see:Using and Nesting Components
[page 508]
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
521
Attribute
Description
models
Defines models that should be created or destroyed along the
component's lifecycle. The key represents the model name.
Use an empty string ("") for the default model.
●
type: Model class name
●
uri: Relative URL in the component, taking
embeddedBy into account if filled, or server for absolute
model
●
settings: Object that is passed to the model construc­
tor
●
dataSource: String of key or alias from sap.app
dataSources to reference an existing data source; the
type, uri and settings properties are set according
to the data source's type, uri and settings (if not
already defined). If the type under sap.app
dataSources is OData, an OData Model V2 is created
automatically. If you need an OData Model V1, specify the
type as well.
●
preload: Optional; Boolean with true, false (de­
fault)
Defines whether or not the model is initialized (pre­
loaded) before the component instance is created and
while loading the component preload and its dependen­
cies.
For more information, see Manifest Model Preload [page
556].
rootView
Specifies the root view that shall be opened; can be the view
name as a string for XML views, or the view configuration ob­
ject with viewName for the view name as a string and type
for the type (enumeration of sap.ui.core.mvc.ViewType), id,
async and other properties of sap.ui.core.mvc.view.
autoPrefixId
true, false (default), Enables the auto prefixing for the UICom­
ponent for IDs of ManagedObjects (controls or elements)
which are created in the context of the createContent
function, or any other invocation of the
Component.prototype.runAsOwner() function (for
example a component’s router uses this method when creat­
ing new views).
In former OpenUI5 releases this prefixing of the ID needed to
be done with oComponent.createId by overwriting the
method getAutoPrefixId. The same can now be ach­
ieved declaratively by setting autoPrefixId to true.
522
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Attribute
Description
handleValidation
Possible values: true or false (default); used to enable or
disable validation handling by the message manager for this
component, see Managing UI and Server Messages [page
791]
config
Static configuration; specify the name-value pairs that you
need in your component.
routing
Provides configuration parameters for route and router, see
Routing and Navigation [page 797]
extends
Used to extend another component.
●
component: ID (namespace) of the component being
extended
●
minVersion: Specifies the minimum version of the
component being extended, for information purposes if
your app requires a minimum version of the component
●
extensions: Component or view extensions, which en­
able you to replace and extend views and controllers and
also to modify the views, see Extending Apps [page 1102]
contentDensities
Mandatory; contains an object with the content density modes
that the app supports, see Content Densities [page 832]
●
compact: Mandatory; indicates whether compact mode
is supported (true, false)
●
cozy: Mandatory; indicates whether cozy mode is sup­
ported (true, false)
resourceRoots
Map of URL locations keyed by a resource name prefix; only
relative paths inside the component are allowed and no ".."
characters
This attribute is intended for actual sub-packages of the com­
ponent only, meaning that it must not be used for the compo­
nent namespace itself. The defined resource roots will be reg­
istered after the component controller is loaded and do not af­
fect the modules being declared as dependencies in the com­
ponent controller.
componentName
Name of the OpenUI5 component
appVariantIdHierarchy
Needed for an app variant scenario to reference UI flex
changes from layers below. An array of appVariantId hier­
archy with origin layer and version, calculated attribute and filled automatically during variant merge.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
523
Attribute
Description
i18n
Determines if the library contains a i18n resource or not. If us­
ing a string instead of a boolean value, an alternative name for
the i18n resource could be defined.
Note
If your SAP Fiori library benefits from the new attribute
sap.ui5/library/i18n, adoption is recommended.
This is only the case if the main resource bundle (proper­
ties file) used by the SAP Fiori Library is different than the
default name messagebundle.properties
Table 14: Attributes in the sap.platform.abap namespace
Attribute
Description
uri
Specifies the app's URI in the ABAP system, for exam­
ple /sap/bc/ui5_ui5/sap/appName; filled during de­
ployment.
Table 15: Attributes in the sap.platform.hcp namespace
Attribute
Description
uri
Specifies the URI inside the SAP Cloud Platform HTML5 appli­
cation; filled during deployment, default is ""
providerAccount
Specifies the name of the provider account; filled during de­
ployment
appName
Specifies the name of the deployed HTML5 application; filled
during deployment
appVersion
Specifies the version of the deployed HTML5 application; filled
during deployment
Table 16: Attributes in the sap.fiori namespace
Attribute
registrationIds
Description
Array of registration IDs, for example, the Fiori IDs for Fiori
apps
archeType
Mandatory arche type of the app, possible values
transactional, analytical, factsheet,
reusecomponent, fpmwebdynpro, designstudio
524
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
_version
● On root level (no namespace) Describes the descriptor format version (mandatory). Needs to be updated
when migrating to a new descriptor format version, see Migrating from Component Metadata to Descriptor
[page 531]
● Inside namespace: Describes the namespace format version (optional from version 1.38 on)
Example
{
"_version": "1.9.0",
"start_url": "index.html",
"sap.app": {
"id": "sap.fiori.appName",
"type": "application",
"i18n": "",
"applicationVersion": {
"version": "1.2.2"
},
"embeds": ["mycomponent1", "subpath/mycomponent2"],
"embeddedBy": "../../",
"title": "{{title}}",
"subTitle": "{{subtitle}}",
"shortTitle": "{{shorttitle}}",
"description": "{{description}}",
"info": "{{info}}",
"tags": {
"keywords": ["{{keyWord1}}", "{{keyWord2}}"],
"technicalAttributes": ["ATTRIBUTE1", "ATTRIBUTE2"]
},
"ach": "PA-FIO",
"dataSources": {
"equipment": {
"uri": "/sap/opu/odata/snce/PO_S_SRV;v=2/",
"type": "OData",
"settings": {
"odataVersion": "2.0",
"annotations": ["equipmentanno"],
"localUri": "model/metadata.xml",
"maxAge": 360
}
},
"equipmentanno": {
"uri": "/sap/bc/bsp/sap/BSCBN_ANF_EAM/BSCBN_EQUIPMENT_SRV.anno.XML",
"type": "ODataAnnotation",
"settings": {
"localUri": "model/annotations.xml"
}
}
},
"cdsViews": [
"VIEW1", "VIEW2"
],
"resources": "resources.json",
"offline": true,
"sourceTemplate": {
"id": "sap.ui.ui5-template-plugin.1worklist",
"version": "1.0.0"
},
"destination": {
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
525
"name": "SAP_ERP_FIN"
},
"openSourceComponents": [{
"name": "D3.js",
"packagedWithMySelf": false
}],
"crossNavigation": {
"scopes": {
"sapSite": {
"value": "123"
}
},
"inbounds": {
"contactCreate": {
"semanticObject": "Contact",
"action": "create",
"icon": "sap-icon://add-contact",
"title": "{{title}}",
"subTitle": "{{subtitle}}",
"shortTitle": "{{shorttitle}}",
"info": "{{info}}",
"displayMode": "HeaderMode",
"indicatorDataSource": {
"dataSource": "equipment",
"path": "TaskListSet/$count",
"refresh": 5
},
"deviceTypes": {
"desktop": true,
"tablet": true,
"phone": false
},
"signature": {
"parameters": {
"id": {
"required": true
},
"ContactName": {
"defaultValue": {
"value": "anonymous"
},
"required": false,
"renameTo": "NAME2"
},
"Gender": {
"filter": {
"value": "(male)|(female)",
"format": "regexp"
},
"required": true,
"renameTo": "SEX",
"launcherValue": {
"value": "female",
"format": "plain",
"prompt": true
}
}
},
"additionalParameters": "ignored"
}
},
"contactDisplay": {
"semanticObject": "Contact",
"action": "display",
"signature": {
"parameters": {
"id": {
"required": true
526
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
}
}
},
"Language": {
"filter": {
"value": "EN"
},
"required": true
},
"SomeValue": {
"filter": {
"value": "4711"
}
},
"GLAccount": {
"defaultValue": {
"value": "1000"
},
"filter": {
"value": "(1000)|(2000)",
"format": "regexp"
}
}
},
"contactDisplayAlt": {
"semanticObject": "Contact",
"action": "display",
"hideLauncher": true,
"signature": {
"parameters": {
"GLAccount": {
"defaultValue": {
"value": "UserDefault.GLAccount",
"format": "reference"
},
"filter": {
"value": "\\d+",
"format": "regexp"
},
"required": true
},
"SomePar": {
"filter": {
"value": "UserDefault.CostCenter",
"format": "reference"
},
"required": true
}
}
}
}
},
"outbounds": {
"addressDisplay": {
"semanticObject": "Address",
"action": "display",
"additionalParameters": "ignored",
"parameters": {
"CompanyName": {
"value": {
"value": "companyName",
"format": "plain"
},
"required": true
}
}
},
"companyDisplay": {
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
527
},
}
}
}
"semanticObject": "Company",
"action": "display",
"additionalParameters": "allowed",
"parameters": {
"CompanyName": {
"value": {
"value": "companyName",
"format": "plain"
},
"required": true
}
}
"sap.ui": {
"technology": "UI5",
"icons": {
"icon": "sap-icon://add-contact",
"favIcon": "icon/F1373_Approve_Purchase_Orders.ico",
"phone": "icon/launchicon/57_iPhone_Desktop_Launch.png",
"phone@2": "icon/launchicon/114_iPhone-Retina_Web_Clip.png",
"tablet": "icon/launchicon/72_iPad_Desktop_Launch.png",
"tablet@2": "icon/launchicon/144_iPad_Retina_Web_Clip.png"
},
"deviceTypes": {
"desktop": true,
"tablet": true,
"phone": false
},
"supportedThemes": [
"sap_hcb"
],
"fullWidth": true
},
"sap.ui5": {
"resources": {
"js": [{
"uri": "component.js"
}],
"css": [{
"uri": "component.css",
"id": "componentcss"
}]
},
"dependencies": {
"minUI5Version": "1.52.0",
"libs": {
"sap.m": {
"minVersion": "1.34.0"
},
"sap.ui.commons": {
"minVersion": "1.34.0",
"lazy": true
}
},
"components": {
"sap.ui.app.other": {
"minVersion": "1.1.0",
"lazy": true
}
}
},
"componentUsages": {
"myusage": {
528
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
"name": "my.used",
"settings": {},
"componentData": {}
}
},
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"uri": "i18n/i18n.properties"
},
"equipment": {
"preload": true,
"dataSource": "equipment",
"settings": {}
}
},
"rootView": {
"viewName": "sap.ui.test.view.Main",
"id" : "rootView",
"async": true,
"type": "XML"
},
"handleValidation": true,
"config": {
},
"routing": {
},
},
"extends": {
"component": "sap.fiori.otherApp",
"minVersion": "0.8.15",
"extensions": {}
},
"contentDensities": {
"compact": true,
"cozy": false
},
"resourceRoots": {
".myname": "./myname"
},
"componentName": "sap.fiori.appName",
"autoPrefixId": true,
"appVariantId": "hcm.leaverequest.oil",
"appVariantIdHierarchy": [
{"layer": "VENDOR", "appVariantId": "abc", "version": "1.0.0"}
],
"services": {
"myLocalServiceAlias": {
"factoryName": "sap.ushell.LaunchPadService",
"optional": true
}
},
"library": {
"i18n": true
}
"sap.platform.abap": {
"uri": "/sap/bc/ui5_ui5/sap/appName",
"uriNwbc": ""
},
"sap.platform.hcp": {
"uri": "",
"uriNwbc": "",
"providerAccount": "fiori",
"appName": "sapfioriappName",
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
529
},
"appVersion": "1.0.0",
"multiVersionApp": true
"sap.fiori": {
"registrationIds": [
"F1234"
],
"archeType": "transactional"
},
}
"sap.mobile": {},
"sap.flp": {},
"sap.ui.generic.app": {},
"sap.ovp": {},
"sap.ui.smartbusiness.app": {},
"sap.wda": {},
"sap.gui": {},
"sap.cloud.portal": {},
"sap.apf": {},
"sap.platform.cf": {},
"sap.map": {},
"sap.fe": {},
"sap.url": {}
The following namespaces are in responsibility of the corresponding teams:
● sap.mobile - in Mobile responsibility
● sap.flp - in Fiori Launchpad responsibility
● sap.ui.generic.app - in SAP Fiori elements responsibility
● sap.ovp - in Overview Page responsibility
● sap.ui.smartbusiness.app - in Smart Business responsibility
● sap.wda - in Web Dypro ABAP responsibility
● sap.gui - in SAP GUI responsibility
● sap.cloud.portal - in SAP Cloud Portal responsibility
● sap.apf - in analysis path framework responsibility
● sap.platform.cf - in cloud foundry/xsa responsibility
● sap.map - in SAP Visual Business responsibility
● sap.fe - in SAP Fiori Elements responsibility
● sap.url - in Fiori Launchpad responsibility
Declaration in Component Metadata
The component declares the existence of the application descriptor by specifying manifest: "json" in the
component metadata. Setting this flag makes the component load the manifest.json file and read the relevant
entries for OpenUI5. This metadata is used to define the dependencies that need to be loaded in order to start the
component. The following code snippet shows how to add the manifest link:
sap.ui.define(['sap/ui/core/UIComponent'], function(UIComponent) {
return UIComponent.extend("sap.samples.Component", {
metadata : {
manifest: "json"
}
530
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
});
});
SAPUI5 API
At runtime, the manifest.json content can be accessed from the component via the component metadata:
// get the component class
sap.ui.require(['sap/samples/Component'], function(SampleComponent) {
// getting complete manifest from component metadata
SampleComponent.getMetadata().getManifest();
//or getting a namespace
SampleComponent.getMetadata().getManifestEntry("sap.app");
});
Related Information
sap.ui.core.UIComponent
Component Metadata [page 505]
Migrating from Component Metadata to Descriptor
Overview, how the component metadata are mapped to the descriptor.
For compatibility reasons, the mapping to the manifest.json file is done automatically. If a metadata property
has been defined, it can also be consumed via the corresponding property of the manifest.json file. For a
detailed step-by-step guide, see Creating a Descriptor File for Existing Apps [page 539].
Note
To benefit from the performance improvements that can be achieved by using “manifest first”, we recommend
to migrate the component metadata to the descriptor (manifest.json). For more information about manifest
first, see the Manifest First Function section in Descriptor for Applications, Components, and Libraries [page
513].
Table 17: Mapping Table
Metadata
Descriptor
Comment
Component namespace
sap.app/id
-
version
sap.app/applicationVersion/
-
version
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
531
Metadata
Descriptor
Comment
config
sap.ui5/config
-
dependencies
sap.ui5/depedencies
Different format, see Dependencies sec­
tion below
customizing
sap.ui5/extends/extensions
-
handleValidation
sap.ui5/handleValidation
-
includes
sap.ui5/resources
Different format, see Resources section
below
rootView
sap.ui5/rootView
-
routing
sap.ui5/routing
-
Dependencies
Libraries and components are objects and not arrays. For the descriptor part, we use ui5version instead of
minUI5Version.
Metadata
"dependencies": {
"ui5version": "1.30.0",
"libs": [
"sap.m",
"sap.ui.unified"
],
"components": [ "sap.app.otherComponent" ]
}
Descriptor
"dependencies": {
"minUI5Version": "1.30.0",
"libs": {
"sap.m": {},
"sap.ui.unified": {}
},
"components": {
"sap.app.otherComponent": {}
}
}
Resources
Includes are renamed to resources and are objects and not an array.
532
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Metadata
"includes": ["script.js", "style.css"]
Descriptor
"resources": {
"js": [
{
"uri": "script.js"
}
],
"css": [
{
"uri": "style.css"
}
]
}
Descriptor for Libraries
The descriptor for libraries contains a subset of the attributes in the descriptor for applications and components.
manifest.json
.library
Available for SAPUI5 dist
Comment
libraries?
sap.app/id
name
x
sap.app/type
-
x
Generated with value
library
sap.app/embeds
-
sap.app/i18n
appData/manifest/
x
Generated
New in .library
i18n
sap.app/
version
x
title
x
applicationVersion/
version
sap.app/title
Text symbol syntax with
leading curly brackets ({{)
and trailing curly brackets
(}}); new in .library
sap.app/description
documentation
x
Text symbol syntax with
leading curly brackets ({{)
and trailing curly brackets
(}})
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
533
manifest.json
.library
Available for SAPUI5 dist
Comment
libraries?
sap.app/ach
appData/ownership/
x
component
sap.app/
New in .library
appData/manifest/
openSourceComponents openSourceComponents
sap.app/resources
-
x
Generated with value
resources.json
sap.app/offline
appData/manifest/
x
New in .library
offline
sap.app/
appData/manifest/
New in .library, to be
sourceTemplate
sourceTemplate
filled by SAP Web IDE only
sap.ui/technology
-
sap.ui/deviceTypes
appData/manifest/
x
Generated with value UI5
New in .library
deviceTypes
sap.ui/
-
x
Generated and merged
-
x
Generated
dependencies
x
supportedThemes
sap.ui5/
dependencies/
minUI5Version
sap.ui5/
dependencies/libs
sap.ui5/
appData/manifest/
contentDensities
contentDensities
sap.platform.abap/ur appData/manifest/
i
New in .library
New in .library
sap.platform.abap/ur
i
sap.platform.hcp/uri appData/manifest/
New in .library
sap.platform.hcp/uri
sap.fiori/
appData/manifest/
registrationIds
sap.fiori/
New in .library
registrationId
sap.fiori/archeType
appData/manifest/
New in .library
sap.fiori/archeType
534
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Related Information
Creating a Descriptor File for Existing Apps [page 539]
Descriptor for Components (Inside Libraries)
The descriptor for components contains a subset of the attributes in the descriptor for applications
Table 18: Attributes in the sap.app namespace
Attribute
Comment
id
Mandatory
type
With value component; mandatory
i18n
Path relative to component; default is "i18n/
i18n.properties"
Path back to library is also possible, for example via "../
i18n/i18n.properties"
embeddedBy
Mandatory, for example, "../"
title
Mandatory
subTitle
description
ach
dataSources
cdsViews
resources
Mandatory; must have value resources.json as file; it is
generated by the library build with this name
offline
sourceTemplate
Table 19: Attributes in the sap.ui namespace
Attribute
Comment
technology
With value UI5; mandatory
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
535
Attribute
Comment
deviceTypes
supportedThemes
Table 20: Attributes in the sap.ui5 namespace
Attribute
Comment
resources
dependencies
libs
components
models
rootView
handleValidation
config
routing
extends
component
minVersion
contentDensities
componentName
Table 21: Attributes in the sap.mobile namespace
Attribute
Comment
definingRequests
Library Name Determination
SAPUI5 determines the library name by analyzing the component namespace (package) up to the part where the
segment starts with a capitalized letter. If the library name that has been determined, does not fit your component,
an additional library attribute needs to be filled in the component metadata in Component.js to specify the
library your component belongs to.
Example:
sap.ui.core.UIComponent.extend("com.sap.fancylibrary.sub.CompLib.Component", {
metadata : {
536
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
...
}
"manifest" : "json",
"library" : "com.sap.fancylibrary",
Resources.json File
The Resources.json file lists all resources in a component or library folder. It resides next to each
manifest.json in the generated results.
The file is generated during build time and its main purpose is for mobile packaging, as resources.json
mentions all files inside the application. If an app has a resources.json file, it is mentioned in the
manifest.json under sap.app/resources.
Note
This file is used by SAP Tools like the SAP Fiori Client Packager. It will be generated automatically when using
SAP WebIDE.
The list of resources is stored in an array in the resources property of the top level JSON object. The top level
object can also contain the _version property, which can be omitted if the value is 1.0.0. For each resource, the
following entries are possible:
Property
Type
Description
name
string
Relative path of the resource as
accessible in a server; starts with the
first name segment, for example
Component.js (mandatory)
isDebug
Boolean
When set to true, the resource is a
debug source, the OpenUI5 build derives
the flag from the naming convention (-
dbg(.controller .view .frag
ment).js) (optional)
locale
string
Locale of the resource for known i18n
resources; the OpenUI5 build derives
the locale from the naming convention
(*_[locale].properties)
(optional)
raw
string
Name of the corresponding resource in
the raw (developer) language for known
i18n resources; for
messagebundle.en.properties,
for example, the corresponding raw file
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
537
Property
Type
Description
is messagebundle.properties
(optional)
merged
boolean
Indicates whether the resource is a
merged resource (optional)
By default, the OpenUI5 build
determines this from naming
conventions (library-
preload.json, library-all.js,
Component-preload.js), but it
also allows to add more merged files by
manual configuration of the build step.
SAP Web IDE may use other knowledge
for this; it knows, for example, that it
merges the Component-
preload.js.
theme
string
Indicates a theme-dependant resource
(optional)
The OpenUI5 build determines this from
the naming convention
**themes<theme>/ **
Example
{
"resources":[
{
"name": ".library"
},
{
"name": ".theming"
},
{
"name": "DynamicSideContent-dbg.js",
"isDebug":true
},
{
"name": "DynamicSideContent.js"
},
{
"name": "DynamicSideContentRenderer-dbg.js",
"isDebug":true
},
...
{
},
"name": "library-preload.json",
"merged":true
...
538
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
{
},
"name": "messagebundle_de.properties",
"raw":"messagebundle.properties",
"locale":"de"
...
{
}
"name": "themes/sap_belize/library.less",
"theme":"sap_belize"
...
]
}
Creating a Descriptor File for Existing Apps
Detailed description of the steps needed to create a descriptor V2 for applications file for an existing transactional
app created by the customer based on SAP Fiori.
1. Create the manifest.json file.
You create the file in the web context root of your app on the same level as the Component.js file, using the
content according to the instructions described from step 2 onwards. You can use the following code sample
as a template. Make sure that you exchange or remove all placeholders (<...>) according to the instructions
below.
{
"_version": "1.1.0",
"start_url": "<startUrl>",
"sap.app": {
"_version": "1.1.0",
"id": "<id>",
"type": "application",
"i18n": "<i18nPathRelativeToManifest>",
"applicationVersion": {
"version": "<version>"
},
"title": "{{<title>}}",
"tags": {
"keywords": [
"{{<keyword1>}}", "{{<keyword2>}}"
]
},
"dataSources": {
"<dataSourceAlias>": {
"uri": "<uri>",
"settings": {
"localUri": "<localUri>"
}
}
}
},
"sap.ui": {
"_version": "1.1.0",
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
539
"icons": {
"icon": "<icon>",
"favIcon": "<favIcon>",
"phone": "<phone>",
"phone@2": "<phone@2>",
"tablet": "<tablet>",
"tablet@2": "<tablet@2>"
},
"deviceTypes": {
"desktop": true,
"tablet": true,
"phone": true
},
"supportedThemes": [
"sap_hcb",
"sap_belize"
]
},
"sap.ui5": {
"_version": "1.1.0",
"resources": {
"js": [
{
"uri": "<uri>"
}
],
"css": [
{
"uri": "<uri>",
"id": "<id>"
}
]
},
"dependencies": {
"minUI5Version": "<minUI5Version>",
"libs": {
"<ui5lib1>": {
"minVersion": "<minVersion1>"
},
"<ui5lib2>": {
"minVersion": "<minVersion2>"
}
},
"components": {
"<ui5component1>": {
"minVersion": "<minComp1Version>"
}
}
},
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"uri": "<uriRelativeToManifest>"
},
"": {
"dataSource": "<dataSourceAlias>",
"settings": {}
}
},
"rootView": "<rootView>",
"handleValidation": <true|false>,
"config": {
},
"routing": {
},
"extends": {
540
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
"component" : "<extendedComponentId>",
"minVersion": "<minComp1Version>",
"extensions": {}
}
"contentDensities": {
"compact": <true|false>,
"cozy": <true|false>
}
}
},
"sap.platform.abap": {
"_version": "1.1.0",
"uri": "<uri>"
},
"sap.platform.hcp": {
"_version": "1.1.0",
"uri": "<uri>"
}
2. Fill the start_url (W3C namespace).
If applicable, replace the <start_url> placeholder with the start URL of your app, for example index.html.
If no start URL is shipped, remove the "start_url" section in the manifest.json file.
{
}
"start_url": "index.html",
...
3. Fill the id and applicationVersion/version attributes of the sap.app namespace.
Caution
id in the sap.app namespace must correspond to the component name in the Component.js file, for
example jQuery.sap.declare("cust.emp.myleaverequests.Component");.
To fill the ID and version information, open the Component.js file of your app and add the ID / namespace
and version information:
jQuery.sap.declare("cust.emp.myleaverequests.Component");
...
metadata : {
"name" : "My Leave Requests",
"version" : "1.2.6"
Open the manifest.json file and enter the values from the Component.js file as follows:
○ Replace the <id> placeholder with the the id / namespace value from jQuery.sap.declare
("cust.emp.myleaverequests.Component" in the example above).
○ Replace the <version> placeholder with the version value ("1.2.6" in the example above).
Example: sap.app/id and sap.app/applicationVersion/version in the manifest.json file:
"sap.app": {
"_version": "1.1.0",
...
"id": "cust.emp.myleaverequests",
...
"applicationVersion": {
"version": "1.2.6"
},
4. Fill the i18n and title attributes of the sap.app namespace.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
541
You find the respective information in the Component.js file under resourceBundle for the i18n attribute,
and under titleResource for the title attribute:
"config" : {
"titleResource": "app.Identity",
"resourceBundle": "i18n/i18n.properties",
Open the manifest.json file and enter the values from the Component.js file as follows:
○ Replace the <title> placeholder with the titleResource value ("app.Identity" in the example
above)
○ Replace the <i18nPathRelativeToManifest> placeholder with the resourceBundle value ("i18n/
i18n.properties" in the example above).
Example: sap.app/i18n and sap.app/title in the manifest.json file
"sap.app": {
"_version": "1.1.0",
...
"i18n": "i18n/i18n.properties",
...
"title": "{{app.Identity}}",
5. Fill the tags/keywords attribute of the sap.app namespace.
If you maintain keywords for the SAP Fiori launchpad tile configuration (optional), enter one or more text
symbols from the sap.app/i18n file in the keywords attribute of the manifest.json file. If not, remove the
tags/keywords section from the manifest.json file.
Example: sap.app/tags/keywords in the manifest.json file
"sap.app": {
"_version": "1.1.0",
...
"tags": {
"keywords": [
"{{Leave}}"
]
},
6. Fill the dataSource attribute of the sap.app namespace with the data source you use for your app.
For this, open the location where the service URL and the mock data source is defined.
○ Open the Component.js file of your app to see the data source under serviceUrl, see the following
example for name, serviceUrl and mock data URL in Component.js:
metadata : {
...
"config" : {
...
"serviceConfig" : {
name: "LEAVEREQUEST",
serviceUrl: "/sap/opu/odata/GBHCM/LEAVEREQUEST;v=2/"
}
},
...
init : function() {
...
oMockServer.simulate(rootPath + "/model/metadata.xml", rootPath + "/
model/");
Return to the manifest.json file and do the following:
○ Enter the name value in the placeholder for <dataSourceAlias>.
542
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
○ Enter the value from the serviceUrl in the placeholder for <uri> to fill the value for the URI attribute.
○ Enter the value from the URI of oMockServer.simulate... in the Component.js file in the placeholder
for <localUri> to fill the value for the localUri attribute.
Example: dataSources with alias and URI in the sap.app namespace of the manifest.json file
"sap.app": {
"_version": "1.1.0",
...
"dataSources": {
"LEAVEREQUEST": {
"uri": "/sap/opu/odata/GBHCM/LEAVEREQUEST;v=2/",
"settings": {
"localUri": "model/metadata.xml"
}
}
}
7. Fill the icons attribute of the sap.ui namespace.
Open the Component.js file of your app to see the icons in the config section.
Example: icons in the Component.js file:
"config" : {
...
"icon": "sap-icon://Fiori2/F0394",
"favIcon": "./resources/sap/ca/ui/themes/base/img/favicon/
My_Leave_Requests.ico",
"homeScreenIconPhone": "./resources/sap/ca/ui/themes/base/img/launchicon/
My_Leave_Requests/57_iPhone_Desktop_Launch.png",
"homeScreenIconPhone@2": "./resources/sap/ca/ui/themes/base/img/launchicon/
My_Leave_Requests/114_iPhone-Retina_Web_Clip.png",
"homeScreenIconTablet": "./resources/sap/ca/ui/themes/base/img/launchicon/
My_Leave_Requests/72_iPad_Desktop_Launch.png",
"homeScreenIconTablet@2": "./resources/sap/ca/ui/themes/base/img/launchicon/
My_Leave_Requests/144_iPad_Retina_Web_Clip.png"
},
Return to the manifest.json file:
○ Enter the icon value in the <icon> placeholder.
○ Enter the favIcon value in the <favIcon> placeholder.
○ Enter the homeScreenIconPhone value in the <phone> placeholder. Do the same for the <phone@2>,
<tablet> and <tablet@2> placeholders.
Example: icons in the sap.ui namespace of the manifest.json file
"sap.ui": {
"_version": "1.1.0",
...
"icons": {
"icon": "sap-icon://Fiori2/F0394",
"favIcon": "./resources/sap/ca/ui/themes/base/img/favicon/
My_Leave_Requests.ico",
"phone": "./resources/sap/ca/ui/themes/base/img/launchicon/
My_Leave_Requests/57_iPhone_Desktop_Launch.png",
"phone@2": "./resources/sap/ca/ui/themes/base/img/launchicon/
My_Leave_Requests/114_iPhone-Retina_Web_Clip.png",
"tablet": "./resources/sap/ca/ui/themes/base/img/launchicon/
My_Leave_Requests/72_iPad_Desktop_Launch.png",
"tablet@2": "./resources/sap/ca/ui/themes/base/img/launchicon/
My_Leave_Requests/144_iPad_Retina_Web_Clip.png"
},
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
543
If your app does not have icons, remove the icons section or the corresponding icon attributes from the
manifest.json file.
8. Fill the deviceTypes and supportedThemes attributes in the sap.ui namespace in the manifest.json
file.
Return to the manifest.json file and ensure that the deviceTypes and supportedThemes attributes in
the manifest.json are correct for your application. If not, adapt the entries accordingly.
Example: deviceTypes and supportedThemes in the sap.ui namespace in the manifest.json file
"sap.ui": {
"_version": "1.1.0",
...
"deviceTypes": {
"desktop": true,
"tablet": true,
"phone": true
},
"supportedThemes": [
"sap_hcb",
"sap_belize"
]
9. Fill the resources attribute in the sap.ui5 namespace.
Open the Component.js file of your app to see the js and CSS resources under includes.
Example: includes in the Component.js file
"includes": ["css/shopStyles.css", "myfile.js"],
Return to the manifest.json file:
○ Enter the js resource value under "js" in the <uri> placeholder.
○ Enter the CSS resource value under "css" in the <uri> placeholder.
Caution
The format in the Component.js file is an array, whereas the format in the manifest.json file is a map.
Example: resources attribute in the sap.ui namespace in the manifest.json file
"sap.ui5": {
"_version": "1.1.0",
...
"resources": {
"js": [
{
"uri": "myfile.js"
}
],
"css": [
{
"uri": "css/shopStyles.css"
}
]
},
If your app does not include resources, remove the resources section from the manifest.json file.
10. Fill the dependecies attribute of the sap.ui5 namespace with the OpenUI5 dependencies that are used.
Open the Component.js file of your app to see the dependencies for the ui5 libs and components.
544
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Example: dependencies in the Component.js file
"dependencies": {
"libs": [
"sap.m",
"sap.me"
],
"components": ["sap.app.otherComponent"]
}
Return to the manifest.json file and fill the corresponding entries in the manifest.json. Enter a value for
the minimum OpenUI5 version in the <ui5Version> placeholder.
Caution
The format in the Component.js file is an array, whereas the format in the manifest.json file is a map.
Ensure that all of the OpenUI5 libraries used by your app are mentioned under libs. Also make sure that all
of the OpenUI5 components used by your app are mentioned under components. If there are no
dependent components, remove the components entry.
Example: dependencies in the sap.ui5 namespace in the manifest.json file
"sap.ui5": {
"_version": "1.1.0",
...
"dependencies": {
"minUI5Version": "1.30",
"libs": {
"sap.m": {
"minVersion": "1.30"
},
"sap.me": {
"minVersion": "1.30"
}
},
"components": {
"sap.app.otherComponent": {
"minVersion": "1.2.0"
}
}
},
If your app requires a minimum version of a lib or component, specify the version under minVersion for
information purposes. If not, remove the minVersion attribute.
11. Fill the models attribute of the sap.ui5 namespace.
If a model is entered in sap.ui5/models in the manifest.json file, OpenUI5 creates the model
automatically and the coding for model creation inside the app can be removed.
Example: model creation in Component.js:
init : function() {
...
// set i18n model
var i18nModel = new sap.ui.model.resource.ResourceModel({
bundleUrl : rootPath + "/i18n/i18n.properties"
});
this.setModel(i18nModel, "i18n");
// set data model
var m = new sap.ui.model.odata.v2.ODataModel(sServiceUrl);
this.setModel(m);
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
545
Return to the manifest.json file:
○ i18n model
Use the same model name as in the Component.js file, for example "i18n", and the type
sap.ui.model.resource.ResourceModel. Enter the URI from the Component.js file in the
<uriRelativeToManifest> placeholder relative to manifest.json, for example, i18n/
i18n.properties
○ OData model
Use the same model name as in the Component.js file, for example "leave" or "" for the default model.
Enter a reference to a data source from sap.app/dataSource in the <dataSourceAlias> placeholder;
if needed, enhance it with more settings for OpenUI5.
Example: Models in the sap.ui5 namespace in the manifest.json file
"sap.ui5": {
"_version": "1.1.0",
...
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"uri": "i18n/i18n.properties"
},
"": {
"dataSource": "LEAVEREQUEST",
"settings": {
}
}
},
12. Fill the rootView, handleValidation, config and routing attributes in the sap.ui5 namespace.
Open the Component.js file of your app to see the rootView, handleValidation, routing, config in the
component metadata section.
Example: rootView, handleValidation, config, routing in sap.ui5 namespace of the
manifest.json file:
...
"rootView": "myRootView",
"handleValidation": true,
"config": {
...
},
"routing": {
...
}
Return to the manifest.json file and copy this metadata from the Component.js file to the sap.ui5
namespace in the manifest.json file.
Only transfer those config parameters in the config section to the manifest.json file that have not yet
been transferred in the steps before. In other words, do not transfer resourceBundle, titleResource,
icon, favicon, homeScreenIconPhone, homeScreenIconPhone2, homeScreenIconTablet and
homeScreenIconTablet2.
Example: rootView, handleValidation, config and routing in the sap.ui5 namespace of the
manifest.json file
"sap.ui5": {
"_version": "1.1.0",
...
"rootView": "myRootView",
"handleValidation": true,
546
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
"config": {
...
},
"routing": {
...
},
If there is no corresponding entry in the Component.js file, remove the section in the manifest.json file.
13. Fill the extends attribute of the sap.ui5 namespace.
Open the Component.js file of your app to see the component which your app extends:
hcm.emp.myleaverequests.Component.extend("cust.emp.myleaverequests.Component", {
Return to the manifest.json file and enter the value from the component namespace in the
<extendedComponentId> placeholder, for example hcm.emp.myleaverequests.
Example: extends/component in sap.ui5 namespace in manifest.json file
"sap.ui5": {
"_version": "1.1.0",
...
"extends": {
"component": "hcm.emp.myleaverequests",
"minVersion": "1.1.0"
}
If your app requires a minimum version of a component, specify the version under minVersion for
information purposes, otherwise remove the attribute. If your app uses the OpenUI5 extension concept with a
customizing entry under component metadata in the Component.js file, move the content of that entry to
sap.ui5/extends/extensions in the manifest.json file, or remove the customizing entry. If your app
does not extend another component, remove the extends section from the manifest.json file.
14. Fill the contentDensities attribute of the sap.ui5 namespace.
Enter the correct values for the compact and cozy attributes (true or false) under contentDensities in
the manifest.json file. The attributes specify the content density modes that your app supports, see
Content Densities [page 832].
Example: contentDensities in sap.ui5 namespace of the manifest.json file:
"sap.ui5": {
"_version": "1.1.0",
...
"contentDensities": {
"compact": true,
"cozy": true
}
15. Verify that no placeholders exist.
Return to the manifest.json file and make sure there are no more placeholders within it (<...>). If the file
still contains placeholders, remove the corresponding sections.
Code Changes
1. Adapt the Component.js file.
Example: Component.js before making changes
jQuery.sap.declare("cust.emp.myleaverequests.Component");
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
547
jQuery.sap.require("cust.emp.myleaverequests.Configuration");
hcm.emp.myleaverequests.Component.extend("cust.emp.myleaverequests.Component", {
metadata : {
"name" : "My Leave Requests",
"version" : "...",
"library" : "cust.emp.myleaverequests",
"includes" : [],
"dependencies" : {
"libs" : ["sap.m", "sap.me"],
"components" : ["sap.app.otherComponent"]
},
"rootView": ...,
"handleValidation": ...,
"config": {
...
},
"routing": {
...
},
"config" : {
"titleResource": "app.Identity",
"resourceBundle": "i18n/i18n.properties",
"icon": "sap-icon://Fiori2/F0394",
"favIcon": "./resources/sap/ca/ui/themes/base/img/favicon/
My_Leave_Requests.ico",
"homeScreenIconPhone": "./resources/sap/ca/ui/themes/base/img/
launchicon/My_Leave_Requests/57_iPhone_Desktop_Launch.png",
"homeScreenIconPhone@2": "./resources/sap/ca/ui/themes/base/img/
launchicon/My_Leave_Requests/114_iPhone-Retina_Web_Clip.png",
"homeScreenIconTablet": "./resources/sap/ca/ui/themes/base/img/
launchicon/My_Leave_Requests/72_iPad_Desktop_Launch.png",
"homeScreenIconTablet@2": "./resources/sap/ca/ui/themes/base/img/
launchicon/My_Leave_Requests/144_iPad_Retina_Web_Clip.png"
},
...
Apply the following changes:
○ Comment or remove the line for the require statement for configuration (if available)
jQuery.sap.require("cust.emp.myleaverequests.Configuration");
○ Add the manifest reference to the metadata: "manifest": "json".
○ Remove the name section.
○ Remove the library section.
○ Remove the version section.
○ Remove the includes section.
○ Remove the dependencies section.
○ Remove the rootView section.
○ Remove the handleValidation section.
○ Remove the routing section.
○ Remove the config section.
Example: Component.js after making changes
jQuery.sap.declare("cust.emp.myleaverequests.Component");
//jQuery.sap.require("cust.emp.myleaverequests.Configuration");
hcm.emp.myleaverequests.Component.extend("cust.emp.myleaverequests.Component", {
metadata : {
"manifest": "json",
...
548
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
2. Adapt the data source reference in the Component.js file.
Example: Data source reference in Component.js file before making changes
metadata : {
...
"config" : {
...
"serviceConfig" : {
name: "LEAVEREQUEST",
serviceUrl: "/sap/opu/odata/GBHCM/LEAVEREQUEST;v=2/"
}
},
...
init : function() {
...
var oServiceConfig = this.getMetadata().getConfig()["serviceConfig"];
var sServiceUrl = oServiceConfig.serviceUrl;
...
oMockServer.simulate(rootPath + "/model/metadata.xml", rootPath + "/model/");
Apply the following changes:
○ Remove serviceConfig under config in the component metadata.
○ If you are still using the service URL in your coding for purposes other than model creation, change the
lines for getting the service config / url and read the URI from the manifest via your component metadata,
for example, this.getMetadata().getManifestEntry("sap.app")...; otherwise, remove that
coding.
○ Change the line for oMockServer.simulate... and read the URI from the manifest via your component
metadata, for example, this.getMetadata().getManifestEntry("sap.app")...
Example: Data source reference in Component.js file after making changes
metadata : {
"manifest": "json",
...
init : function() {
...
var sServiceUrl =
this.getMetadata().getManifestEntry("sap.app").dataSources["LEAVEREQUEST"].uri;
...
oMockServer.simulate(rootPath + "/" +
this.getMetadata().getManifestEntry("sap.app").dataSources["LEAVEREQUEST"].settin
gs.localUri, rootPath + "/model/");
3. Remove the OpenUI5 model creation in the Component.js file.
Example: Component.js file before making changes
init : function() {
...
// set i18n model
var i18nModel = new sap.ui.model.resource.ResourceModel({
bundleUrl : rootPath + "/i18n/i18n.properties"
});
this.setModel(i18nModel, "i18n");
// set data model
var m = new sap.ui.model.odata.v2.ODataModel(sServiceUrl);
this.setModel(m);
Apply the following changes:
○ Delete the lines for the i18n model creation and model setting.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
549
○ Delete the lines for the data model creation and model setting.
Smoke Test
To verify that your app works as before, perform checks to make sure the following is true:
● OData service works as before
● Mock data works as before
● Title, icons in SAP Fiori launchpad work as before
● Navigation works as before
Migration Information for Upgrading the Descriptor File
Information how to add new attributes of descriptor versions higher than V2 (OpenUI5 1.30) to the descriptor file.
Attribute
Version*
Description
_version
V3 (1.32)
Needs to be updated in the
Example
manifest.json file when
migrating to a new descriptor
version:
●
{
"_version":
"1.4.0",
"sap.app": {
...
_version for V3 is
1.2.0
●
_version for V4 is
1.3.0
●
_version for V5 is
1.4.0 (see example)
sap.app/
crossNavigation
V3 (1.32)
Contains navigation
information and is a
mandatory attribute in the
manifest.json file for
SAP Fiori apps; the attribute
contains two sections:
●
sap.app/
crossNavigation/
inbounds - Contains
inbound intents and
signature information
●
sap.app/
crossNavigation/
outbounds - Contains
550
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Attribute
Version*
Description
Example
required intents that are
called explicitely by the
app, for example, if a
business process is split
among different apps A
and B. If A calls B, A has
outbound the intent to
address B.
sap.app/subTitle
V4 (1.34)
Added to the
manifest.json file by
using the {{...}} syntax
Note
Text symbols must be part
"sap.app": {
"_version":
"1.3.0",
...
"title":
"{{title}}",
"subTitle":
"{{subtitle}}",
of the properties file which
is defined in sap.app/
i18n (default "i18n/
i18n.properties").
sap.app/
V4 (1.34)
Used to overwrite the
crossNavigation/
subTitle attribute per
inbounds/
inbound; use the {{...}}
<inboundname>/
syntax to add the attribute to
subTitle
the manifest.json file
Note
Text symbols must be part
"sap.app": {
"_version":
"1.3.0",
...
"crossNavigation":
{
"inbounds": {
"contactCreate":
of the properties file which
{
is defined in sap.app/
i18n (default "i18n/
i18n.properties").
"semanticObject":
"Contact",
"action":
"create",
"icon": "sapicon://addcontact",
"title":
"{{title}}",
"subTitle":
"{{subtitleOther}}
",
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
551
Attribute
Version*
Description
sap.ui/fullWidth
V4 (1.34)
Indicates whether an app
Example
shall run in full screen mode
(true)
If dependencies/
Example for
dependencies/
components/
manifest.json for the
components/
<componentname>/lazy SAP Fiori app:
<componentname>/lazy
and dependencies/
and dependencies/
libs/<libname>/lazy
libs/<libname>/lazy
are set to true, the attribute
sap.ui5/
V4 (1.34)
"sap.ui": {
"_version":
"1.3.0",
"technology":
"UI5",
...
"fullWidth":
true
indicates in an SAP Fiori app
that a dependency shall be
"sap.ui5": {
"_version":
"1.2.0",
...
"dependencies": {
lazy loaded (default is
false), see the example for
manifest.json for the
SAP Fiori app.
"minUI5Version":
"1.34.0",
"libs": {
"sap.m": {
"minVersion":
"1.34.0"
},
"sap.ui.commons":
{
"minVersion":
"1.34.0",
"lazy": true
},
}
"components": {
"sap.ui.app.other"
: {
"minVersion":
"1.1.0"
"lazy": true
},
sapui5/routing/
config/async
V4 (1.34)
General setting for routing
that indicates how the views
are loaded; if set to true, the
views are loaded
552
}
}
"sap.ui5": {
"_version":
"1.2.0",
...
"routing": {
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Attribute
Version*
Description
asynchronously (default is
false)
For performance reasons, we
recommend to always use the
async setting. This
recommendation implies that
Example
{
"config":
"viewType": "XML",
"async": true
...
},
...
you have followed the
OpenUI5 programming model
in general and do not rely on
any sync-execution
depending event-orders.
sap.ui5/models/
V5 (1.38)
preload
Defines whether or not the
model is initialized
(preloaded) before the
component instance is
created and while loading the
component preload and its
"equipment": {
"preload":
true,
"dataSource":
"equipment",
...
}
dependencies
* Available as of descriptor version (OpenUI5 version)
Descriptor Dependencies to Libraries and Components
Description of the performance-relevant attributes that are available for the descriptor for applications,
components and libraries
The performance-relevant attributes have been introduced with the version 3 of the descriptor for applications,
components, and libraries.
Dependencies to Libraries
The following dependencies to libraries can be implemented:
● To benefit from the asynchronous library preload, add the mandatory libraries to sap.ui5/dependencies/
libs.
● To expose the necessary dependencies for offline packages for mobile devices, add optional libraries to
sap.ui5/dependencies/libs and flag them as lazy.
For applications and components, modify the manifest.json as follows:
"sap.ui5": {
...
"dependencies": {
...
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
553
"libs": {
"sap.m": {},
"sap.suite.ui.commons": {
"lazy": true
}
}
...
},
...
For libraries, modify the .library file as shown in the follown code sample. This file is available because the
manifest.json for libraries is generated based on this metadata.
<dependencies>
<dependency>
<libraryName>sap.m</libraryName>
</dependency>
<dependency>
<libraryName>sap.suite.ui.commons</libraryName>
<lazy>true</lazy>
</dependency>
...
In a second step, modify the library.js file as follows:
sap.ui.getCore().initLibrary({
...
dependencies : ["sap.ui.core","sap.m"], // lazy libs are not declared here
Note
In all cases, the lazy libraries need to be loaded manually in the application or library via the loadLibrary API:
// lazy lib loaded synchronously (avoid if possible!)
sap.ui.getCore().loadLibrary("sap.suite.ui.commons");
// lazy lib loaded asynchronously (the preferred way!)
sap.ui.getCore().loadLibrary("sap.suite.ui.commons", { async: true }).then(...);
Tip
Execute the loadLibrary before any resource of the library is required to preload the complete library instead
of loading each resource individually.
Always use the async API as this is the preferred and performant way. Only use the sync API as an exception if
your coding relies on synchronous loading.
Dependencies to Components
Scenario 1: UI library contains multiple components
In this scenario, the library is the leading container and no component preload is available. This means, that you
maintain the library dependency as described above. This is true for all kinds of component dependencies, also for
sap.ui5/extends/component. If the extended component originates in a library, do not use
554
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
sap.ui5.extends/component, but only declare the library dependency. Otherwise, the component dependency
causes a 404 request.
For loading lazy components inside a library, proceed with the library mechanisms as described above:
// lazy lib loaded synchronously (avoid if possible!)
sap.ui.getCore().loadLibrary("sap.suite.ui.commons");
// lazy lib loaded asynchronously (the preferred way!!!)
sap.ui.getCore().loadLibrary("sap.suite.ui.commons", { async: true }).then(...);
Scenario 2: Standalone component
In this scenario, you only maintain a dependency to the component. The component preload is available for this
scenario:
● To benefit from the asynchronous components preload, add the mandatory components to sap.ui5/
dependencies/components
● Add the optional components to sap.ui5/dependencies/components and flag them as lazy.
For applications and components, modify the manifest.json as follows:
"sap.ui5": {
...
"dependencies": {
...
"libs": {
...
},
"components": {
"samples.components.sample": {},
"samples.components.samplelazy": {
"lazy": true
}
...
}
},
...
For loading the lazy standalone components, use the component factory:
sap.ui.component({
...
async: true // use async: true if the Component creation should be done
asynchronously
});
Related Information
loadLibrary
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
555
Manifest Model Preload
The preload flag enables a preload mode for a model, thus improving the startup performance of an app or
component.
The preload flag is located in manifest.json under sap.ui5/models:
"sap.ui5": {
...
"models": {
"mymodel": {
"preload": true,
...
The flag is not active by default, as there are some prerequisites:
● sap.ui.component is set to "async=true" and manifest (API parameter name of sap.ui.component)
● Model class is loaded before sap.ui.component is called; otherwise the model will not be created
● As model events (for example attachMetadataLoaded) may be missed because they are fired before the
component coding runs. we recommend to use the Promise API (e.g. metadataLoaded) instead, depending
on the model type.
● Use the model preload flag for sap.ui.model.resource.ResourceModel if one of the following applies:
○ There is no component preload.
○ The corresponding resource files are not part of the component preload.
● Activating preload for models of type sap.ui.model.odata.v4.ODataModel does not affect the loading
performance as no metadata is required anymore.
This means: The preload flag only makes sense for models which load their data from other locations than the
component itself. Local JSON, XML or resource model does not make sense as it interferes with the component
preload which will result in loading the model data twice and should be omitted. But for the V2 OData model, for
example, using preload speeds up the performance as the OData metadata can already be loaded in parallel to the
component preload.
Before enabling the preload for the V2 ODataModel, make sure that you listen properly to metadata loaded by
using the Promise API instead of the Event API (metadataLoaded) since the preload could have loaded the
metadata already before the application code is executed. The Promise will be executed even if the metadata
loaded event has been raised already.
Listen properly to metadata loaded by using the Promise:
oModel.metadataLoaded().then(function() {
*/ });
556
/* TODO: add the event handling here!
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Enabling the Automatic SAP Fiori 2.0 Header Adaptation in the
Descriptor
Application developers can enable automatic adaptation of their existing applications from the manifest.json app
descriptor. This helps to easily convert applications to the new look-and-feel of SAP Fiori 2.0.
SAP Fiori 2.0
SAP Fiori 2.0 is the next evolution step of the SAP Fiori UX. SAP Fiori 2.0 features new themes, a more unified user
experience, and smoother, more intuitive application interactions.
Application headers, written based on older SAP Fiori design guidelines, can now be easily adapted to the new SAP
Fiori 2.0 look-and-feel by using the automatic adaptation mechanism in the app descriptor.
The SAP Fiori 2.0 Header
The SAP Fiori 2.0 design concept requires changes with regards to the headers of applications and the SAP Fiori
launchpad (FLP). If your application has a header, it needs to be merged into the standardized SAP Fiori 2.0
header. OpenUI5 offers an adapter mechanism to let existing apps automatically adjust their header layout
according to the SAP Fiori 2.0 guidelines.
Note
The screenshots in this topic are mockups and are used to visually outline the adaptations. The final apps will
look somewhat different.
Figure 136: SAP Fiori 2.0 header of a fullscreen application
The complete adaptation of a fullscreen app to SAP Fiori 2.0 consists of five main steps:
1. Remove the app-specific header bar. The header is made transparent and collapsed if there is no content in it
after the adaptation.
2. Display the title in the center of the FLP header
3. Move the action buttons from the app header to the header content area below the FLP header.
4. Move the Back button from the app-specific header to the FLP header.
5. Drill-down hierarchy levels can be added to the dropdown menu adjacent to the FLP title.
You can see how the elements are moved and transformed from the old SAP Fiori version (below) to the new SAP
Fiori 2.0 design in the screenshot below.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
557
Figure 137: SAP Fiori 2.0 header adaptation
Note
These adaptations are primarily valid only for fullscreen apps. Other floorplans, like Master-Detail, are affected
differently and the adaptation there will not be the same.
Enabling the Adaptation in the App Descriptor
You can override the adapter default behavior for a single application by adding an entry in the app descriptor in
the sap.ui5/config section. Setting sapFiori2Adaptation to true enables the full functionality of the SAP
Fiori 2.0 Adapter.
"config": {
}
...
"sapFiori2Adaptation": true,
...
Alternatively, you can use five fine-grained settings to enable only some of the adaptations. In the following
example, you can see how to trigger transparent headers (style attribute) and title propagation to FLP (title
attribute). The other adaptations are not applied.
"config": {
}
...
"sapFiori2Adaptation": {
"style": true,
"collapse": false,
"title": true,
"back": false,
"hierarchy": false
},
...
In the list below, you can see what each of the settings enables.
● style - Triggers header transparency
558
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
● collapse - Triggers collapsing of the header when empty
● title - Triggers moving the header to FLP
● back - Triggers the Back button visibility in the app
● hierarchy - Triggers propagation of the hierarchy to FLP
Note
In rare cases this automatic adaptation of the header area may not work, due to the application structure or
other reasons. In this case the headers will still appear in the old design, but the apps will continue to be usable.
Some old SAP Fiori applications do not have an app descriptor yet. If you consider the effort to provide proper app
descriptors for all applications as too high, there is a second way to do this configuration. This alternative
configuration is done in the metadata section of Component.js (the app’s root component), which also has a
config section. The configuration options can be done there in the same manner.
Note
If both the metadata and manifest are configured, and contradict each other, the configuration in manifest.json
is applied.
Model View Controller (MVC)
The Model View Controller (MVC) concept is used in OpenUI5 to separate the representation of information from
the user interaction. This separation facilitates development and the changing of parts independently.
Model, view, and controller are assigned the following roles:
● The view is responsible for defining and rendering the UI.
● The model manages the application data.
● The controller reacts to view events and user interaction by modifying the view and model.
● Models [page 560]
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
559
● Controller [page 669]
● Views [page 653]
The purpose of data binding in the UI is to separate the definition of the user interface (view), the data visualized
by the application (model), and the code for the business logic for processing the data (controller). The separation
has the following advantages: It provides better readability, maintainability, and extensibility and it allows you to
change the view without touching the underlying business logic and to define several views of the same data.
Views and controllers often form a 1:1 relationship, but it is also possible to have controllers without a UI, these
controllers are called application controllers. It is also possible to create views without controllers. From a technical
position, a view is a OpenUI5 control and can have or inherit a OpenUI5 model.
View and controller represent reusable units, and distributed development is highly supported.
Models
A model in the Model View Controller concept holds the data and provides methods to retrieve the data from the
database and to set and update data.
OpenUI5 provides the following predefined models:
● OData model:
Enables binding of controls to data from OData services. The OData model supports two-way (default), oneway and one-time binding modes. However, two-way binding is currently only supported for properties, and
not for aggregations.
Note
The OData model currently supports the following OData versions:
○ OData V2
○ OData V4 (limited feature scope)
● The JSON model can be used to bind controls to JavaScript object data, which is usually serialized in the
JSON format. The JSON model is a client-side model and, therefore, intended for small data sets, which are
completely available on the client. The JSON model supports two-way (default), one-way and one-time binding
modes.
● XML model:
A client-side model intended for small data sets, which are completely available on the client. The XML model
does not contain mechanisms for server-based paging or loading of deltas. The XML model supports two-way
(default), one-way and one-time binding modes.
● Resource model:
Designed to handle data in resource bundles, mainly to provide texts in different languages. The resource
model only supports one-time binding mode because it deals with static texts only.
560
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
The JSON model, XML model, and the resource model are client-side models, meaning that the model data is
loaded completely and is available on the client. Operations such as sorting and filtering are executed on the client
without further server requests.
The OData (V2 or V4) model is a server-side model and only loads the data requested by the user interface from
the server.
You can not only define one model for your applications, but define different areas in your application with different
models and assign single controls to a model. You can also define nested models, for example, a JSON model
defined for the application and an OData model for a table control contained in the application.
A Web application should support several data sources, such as JSON, XML, Atom, or OData. However, the way in
which data binding is defined and implemented within the UI controls should be independent of the respective
data source. It is also possible to create a custom model implementation for data sources that are not yet covered
by the framework or are domain-specific.
Related Information
Data Binding [page 673]
API Reference: sap.ui.model
API Reference: sap.ui.model.json.JSONModel
API Reference: sap.ui.model.odata.v4.ODataModel
API Reference: sap.ui.model.odata.v4.ODataModel
API Reference: sap.ui.model.resource.ResourceModel
API Reference: sap.ui.model.xml.XMLModel
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
561
OData V2 Model
The OData V2 Model enables binding of controls to data from OData services.
The OData model is a server-side model, meaning that the data set is only available on the server and the client
only knows the currently visible (requested) data. Operations, such as sorting and filtering, are done on the server.
The client sends a request to the server and shows the returned data.
Note
Requests to the back end are triggered by list bindings (ODataListBinding), context bindings
(ODataContextBinding), and CRUD functions provided by the ODataModel. Property bindings
(ODataPropertyBindings) do not trigger requests.
The OData model currently supports OData version 2.0.
The following two versions of the OData model are implemented: sap.ui.model.odata.ODataModel and
sap.ui.model.odata.v2.ODataModel. The v2.ODataModel has an improved feature set and new features
will only be implemented in this model. sap.ui.model.odata.ODataModel is deprecated. We recommend to
only use v2.ODataModel.
The following table shows the supported features for both OData models:
Feature
sap.ui.model.odata.v2.OData
sap.ui.model.odata.ODataMod
Model
el
OData version support
2.0
2.0
JSON format
Yes (default)
Yes
XML format
Yes
Yes (default)
Support of two-way binding mode
Yes; for property changes only, not yet
Experimental; only properties of one
implemented for aggregations
entity can be changed at the same time
Default binding mode
One-way binding
One-way binding
Client-side sorting and filtering
Yes
No
For more information, see API
Reference:
sap.ui.model.odata.OperationMode.
$batch
Yes; all requests can be batched
Only manual batch requests are possible
Data cache in model
All data is cached in the model
Manually requested data is not cached
Automatic refresh
Yes (default)
Yes
Message handling
Yes, see Managing UI and Server
No
Messages [page 791]
562
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Note
Be aware of the Same-Origin-Policy security concept which prevents access to back ends on different domains
or sites.
The requests to the service to fetch data are made automatically based on the data bindings that are defined for
the controls.
Related Information
API Reference: sap.ui.model.odata.v2.ODataModel
Creating the Model Instance
One OData model instance can only cover one OData service. For accessing multiple services, you have to create
multiple OData model instances.
The only mandatory parameter when creating an ODataModel instance is the service URL. It can be passed as
first parameter or within the mParameters map to the constructor.
var oModel = new sap.ui.model.odata.v2.ODataModel("http://services.odata.org/
Northwind/Northwind.svc/");
var oModel = new sap.ui.model.odata.v2.ODataModel({serviceUrl: "http://
services.odata.org/Northwind/Northwind.svc"});
When creating an ODataModel instance, a request is sent to retrieve the service metadata:
http://services.odata.org/Northwind/Northwind.svc/$metadata
Service Metadata
The service metadata is cached per service URL. Multiple OData models that are using the same service can share
this metadata.
Only the first model instance triggers a $metadata request. A JSON representation of the service metadata can
be accessed by calling the getServiceMetadata() method on an Odata model instance.
var oMetadata = oModel.getServiceMetadata();
Note
In the v2.ODataModel, the service metadata is loaded asynchronously. It is not possible to load it
synchronously. To get notified when the loading is finished, attach the metadataLoaded event.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
563
Adding Additional URL Parameters
For OData services, you can use URL parameters for configuration. OpenUI5 sets most URL parameters
automatically, according to the respective binding.
For authentication tokens or general configuration options, for example, you can add additional arguments to the
request URL. Some of the parameters must not be included in every request, but should only be added to specific
list or context bindings, such as $expand or $select. For this, the binding methods provide the option to pass a
map of parameters, which are then included in all requests for this specific binding. The OData model currently
only supports $expand and $select.
There are different ways to add URL parameters to the requests:
● Appending parameters to the service URL:
var oModel = new sap.ui.model.odata.v2.ODataModel("http://myserver/
MyService.svc/?myParam=value&myParam2=value");
These parameters will be included in every request sent to the OData server.
● Passing URL parameters with the mparameters map
You can pass URL parameters that are used for $metadata requests only (metadataUrlParams) as well as
URL parameters that are included only in data requests (serviceUrlParams). The parameters are passed as
maps:
var oModel = new sap.ui.model.odata.v2.ODataModel({
serviceUrl: "http://services.odata.org/Northwind/Northwind.svc",
serviceUrlParams: {
myParam: "value1",
myParam2: "value2"
},
metadataUrlParams: {
myParam: "value1",
myParam2: "value2"
}
});
Custom HTTP Headers
You can add custom headers which are sent with each request.
To do this, provide a map of headers to the OData model constructor or use the setHeaders() function:
● Passing custom headers with the mparameters map
var oModel = new sap.ui.model.odata.v2.ODataModel({
headers: {
"myHeader1" : "value1",
"myHeader2" : "value2"
}
});
● Setting custom headers globally on a model instance
oModel.setHeaders({"myHeader1" : "value1", "myHeader2" : "value2"});
564
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Note
When you add custom headers, all previous custom headers are removed if not specified again in the
headers map. Some headers are private, that is, they are set by the OData model internally and cannot be
set:
"accept"
"accept-language"
"maxdataserviceversion"
"dataserviceversion"
"x-csrf-token"
For additional methods and parameters, see the API Reference: sap.ui.model.odata.v2.ODataModel.
Addressing Entities: Binding Path Syntax
The binding path syntax for OData models matches the URL path relative to the service URL used in OData to
access specific entities or entity sets.
You access the data provided by the OData model according to the structure of the OData service as defined in the
metadata of a service. URL parameters, such as filters, cannot be added to a binding path. A binding path can be
absolute or relative. Absolute binding paths are resolved immediately. A relative path can only be resolved if it can
be automatically converted into an absolute binding path. If, for example, a property is bound to a relative path and
the parent control is then bound to an absolute path, the relative property path can e resolved to an absolute path.
The following binding samples within the ODataModel are taken from the Northwind demo service.
Absolute binding path (starting with a slash ('/')):
"/Customers"
"/Customers('ALFKI')/Address"
Relative binding paths that can be resolved with a context (for example "/Customer('ALFKI')"):
"CompanyName"
"Address"
"Orders"
Resolved to:
"/Customer('ALFKI')/CompanyName"
"/Customer('ALFKI')/Address"
"/Customer('ALFKI')/Orders"
Navigation properties, used to identify a single entity or a collection of entities:
"/Customers('ALFKI')/Orders"
"/Products(1)/Supplier"
For more information on addressing OData entries, see the URI conventions documentation on http://
www.odata.org.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
565
Accessing Data from an OData Model
The data requested from an OData service is cached in the OData model.
It can be accessed by the getData() and the getProperty() method, which returns the entity object or value.
These methods do not request data from the backend, so you can only access already requested and cached
entities:
oModel.getData("/Customer('ALFKI')");
oModel.getProperty("/Customer('ALFKI')/Address");
You can only access single entities and properties with these methods. To access entity sets, you can get the
binding contexts of all read entities via a list binding. The values returned by these methods are copies of the data
in the model, not references as in the JSONModel.
Caution
Do not modify objects or values inside the model manually; always use the provided API to change data in the
model, or use two-way binding (see Two-way Binding section below).
Creating Entities
To create entities for a specified entity set, call the createEntry() method. The method returns a context object
that points to the newly created entity.
The application can bind against these objects and change the data by means of two-way binding. To store the
entities in the OData backend, the application calls submitChanges(). To reset the changes, the application can
call the deleteCreatedEntry() method.
The application can choose the properties that shall be included in the created object and can pass its own default
values for these properties. Per default, all property values are be empty, that is, undefined.
Note
The entity set and the passed properties must exist in the metadata definition of the OData service.
// create an entry of the Products collection with the specified properties and
values
var oContext = oModel.createEntry("/Products", { properties: { ID:99,
Name:"Product", Description:"new Product", ReleaseDate:new Date(), Price:"10.1",
Rating:1} });
// binding against this entity
oForm.setBindingContext(oContext);
// submit the changes (creates entity at the backend)
oModel.submitChanges({success: mySuccessHandler, error: myErrorHandler});
// delete the created entity
oModel.deleteCreatedEntry(oContext);
If created entities are submitted, the context is updated with the path returned from the creation request and
the new data is imported into the model. So the context is still valid and points to the new created entity.
566
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
CRUD Operations
The OData model allows manual CRUD (create, read, update, delete) operations on the OData service. If a manual
operation returns data, the data is imported into the data cache of the OData model. All operations require a
mandatory sPath parameter as well as an optional mParameters map.
The create and update methods also require a mandatory oData parameter for passing the created or changed
data object. Each operation returns an object containing a function abort, which can be used to abort the request.
If the request is aborted, the error handler is called. This ensures that the success or the error handler is executed
for every request. It is also possible to pass additional header data, URL parameters, or an eTag.
● Creating entities
The create function triggers a POST request to an OData service which was specified at creation of the OData
model. The application has to specify the entity set, in which the new entity and the entity data is to be
created.
var oData = {
ProductId: 999,
ProductName: "myProduct"
}
oModel.create("/Products", oData, {success: mySuccessHandler, error:
myErrorHandler});
● Reading entities
The read function triggeres a GET request to a specified path. The path is retrieved from the OData service
which was specified at creation of the OData model. The retrieved data is returned in the success callback
handler function.
oModel.read("/Products(999)", {success: mySuccessHandler, error:
myErrorHandler});
● Updating entities
The update function triggers a PUT/MERGE request to an OData service which was specified at creation of the
OData model. After a successful request to update the bindings in the model, the refresh is triggered
automatically.
var oData = {
ProductId: 999,
ProductName: "myProductUpdated"
}
oModel.update("/Products(999)", oData, {success: mySuccessHandler, error:
myErrorHandler});
● Deleting entities
The remove function triggers a DELETE request to an OData service which was specified at creation of the
OData model. The application has to specify the path to the entry which should be deleted.
oModel.remove("/Products(999)", {success: mySuccessHandler, error:
myErrorHandler});
● Refresh after change
The model provides a mechanism to automatically refresh bindings that depend on changed entities. If you
carry out a create, update or remove function, the model identifies the bindings and triggers a refresh for
these bindings. If the model runs in batch mode, the refresh requests are bundled together with the changes in
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
567
the same batch request. You can disable the auto refresh by calling setRefreshAfterChange(false). If the
auto refresh is disabled, the application has to take care of refreshing the respective bindings.
oModel.setRefreshAfterChange(false);
Concurrency Control and ETags
OData uses HTTP ETags for optimistic concurrency control. The service must be configured to provide them. The
ETag can be passed within the parameters map for every CRUD request. If no ETag is passed, the ETag of the
cached entity is used, if it is loaded already.
XSRF Token
To address cross-site request forgery, an OData service may require XSRF tokens for change requests by the client
application. In this case, the client has to fetch a token from the server and send it with each change request to the
server. The OData model fetches the XSRF token when reading the metadata and then automatically sends it with
each write request header. If the token is no longer valid, a new token can be fetched by calling the
refreshSecurityToken function on the OData model. The token is fetched with a request to the service
document. To ensure getting a valid token, make sure that the service document is not cached.
Refreshing the Model
The refresh function refreshes all data within an OData model. Each binding reloads its data from the server. For
list or context bindings, a new request to the back end is triggered. If the XSRF token is no longer valid, it has to be
fetched again with a read request to the service document. Data that has been imported via manual CRUD
requests is not reloaded automatically.
Batch Processing
The v2.ODataModel supports batch processing ($batch) in two different ways:
● Default: All requests in a thread are collected and bundled in batch requests, meaning that request is sent in a
timeout immediately after the current call stack is finished. This includes all manual CRUD requests as well as
requests triggered by a binding.
● Deferred: The requests are stored and can be submitted with a manual submitChanges() call by the
application. This also includes all manual CRUD requests as well as requests triggered by a binding.
The model cannot decide how to bundle the requests. For this, OpenUI5 provides the groupId. For each binding
and each manual request, a groupId can be specified. All requests belonging to the same group are bundled into
568
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
one batch request. Request without a groupId are bundled in the default batch group. You can use a
changeSetId for changes. The same principle applies: Each change belonging to the same changeSetId is
bundled into one changeSet in the batch request. Per default, all changes have their own changeSet. For more
information, see the API reference.
You can use the setDeferredGroups() method to set a previously defined subset of groups to deferred.
Note
The same is also valid for setChangeGroups() and getChangeGroups().
All requests belonging to the group are then stored in a request queue. The deferred batch group must then be
submitted manually by means of the submitChanges() method. If you do not specify a batch group ID when
calling submitChanges, all deferred batch groups are submitted.
Example
Set a subset of groups to deferred
var oModel = new sap.ui.model.odata.v2.ODataModel(myServiceUrl);
Pass the groupId to a binding:
{path:"/myEntities", parameters: {groupId: "myId"}}
Set the groupId to deferred:
1. Get the list of deferred groups:
var aDeferredGroups = oModel.getDeferredGroups();
2. Append your groupId to the list:
aDeferredGroups=aDeferredGroups.concat(["myId"]);
3. Set all groups to deferred:
oModel.setDeferredGroups(aDeferredGroups);
Submit all deferred groups:
oModel.submitChanges({aDeferredGroups, success: mySuccessHandler, error:
myErrorHandler});
Two-way Binding
The v2.ODataModel enables two-way binding. Per default, all changes are collected in a batch group called
"changes" which is set to deferred.
To submit the changes, use submitChanges(). The data changes are made on a data copy. This enables you to
reset the changes without sending a new request to the backend to fetch the old data again. With
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
569
resetChanges() you can reset all changes. You can also reset only specific entities by calling resetChanges
with an array of entity paths.
Note
Filtering and sorting is not possible if two-way changes are present as this would cause inconsistent data on the
UI. Therefore, before you carry out sorting or filtering, you have to submit or reset the changes.
You can collect the changes for different entities or types in different batch groups. To configure this, use the
setChangeGroups() method of the model:
var oModel = new sap.ui.model.odata.v2.ODataModel(myServiceUrl);
oModel.setDeferredGroups(["myGroupId", "myGroupId2"]);
oModel.setChangeGroups({
"EntityTypeName": {
groupId: "myGroupId",
[changeSetId: "ID",]
[single: true/false,]
}
)};
oModel.submitChanges({groupId: "myGroupId", success: mySuccessHandler, error:
myErrorHandler});
To collect the changes for all entity types in the same batch group, use '*’ as EntityType. If the change is not set
to deferred, the changes are sent to the backend immediately. By setting the single parameter for changeSet to
true or false, you define if a change is assigned its own change set (true) or if all changes are collected in one
change set (false). The model only takes care of the changeSetId if single is set to false.
Note
The first change of an entity defines the order in the change set.
Example
Reset changes:
var oModel = new sap.ui.model.odata.v2.ODataModel(myServiceUrl);
//do a change
oModel.setProperty("/myEntity(0)", oValue);
//reset the change
oModel.resetChanges(["/myEntity(0)"]);
Binding-specific Parameters
The OData protocol specifies different URL parameters.
You can use these parameters in bindings in addition to the parameters described above:
● Expand parameter
The expand parameter allows the application to read associated entities with their navigation properties:
oControl.bindElement("/Category(1)", {expand: "Products"});
oTable.bindRows({
path: "/Products",
570
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
});
parameters: {expand: "Category"}
In this example, all products of "Category(1)" are embedded inline in the server response and loaded in one
request. The category for all "Products" is embedded inline in the response for each product.
● Select parameter
The select parameter allows the application to define a subset of properties that is read when requesting an
entity.
oControl.bindElement("/Category(1)", {expand: "Products", select:
"Name,ID,Products"});
oTable.bindRows({
path: "/Products",
parameters: {select: "Name,Category"}
});
In this example, the properties Name, ID and ofCategory(1) as well as all properties of the embedded
products are returned. The properties Name and Category are included for each product. The Category
property contains a link to the related category entry.
● Custom query options
You can use custom query options as input parameters for service operations. When creating the list binding,
specify these custom parameter as follows:
oTable.bindRows({
path: "/Products",
parameters: {
custom: {
param1: "value1",
param2: "value2"
}
},
template: rowTemplate
});
If you use bindElement, you can specify custom parameters as follows:
oTextField.bindElement("/GetProducts", {
custom: {
"price" : "500"
}
});
Optimizing Dependent Bindings
The ODataModel V2 supports a flag called "preliminaryContext". With this option set to true, the ODataModel is
able to bundle the OData calls for dependent bindings together into fewer $batch requests.
Introduction
Two bindings are considered "dependent" if one cannot be resolved without the other being resolved first, for
example a relative binding cannot be resolved without a resolved absolute binding.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
571
If the preliminaryContext option is set to false, each binding will be resolved once its preceding binding has
been resolved or if it is an absolute binding itself.
The preliminaryContext option can also be activated/deactivated per binding instance. This overwrites the
default value set on the ODataModel instance.
Settings and Usage
ODataModel v2
The constructor parameter is named preliminaryContext (type Boolean) and has the following properties:
● Default value is false.
● It is used by the ContextBinding as a default value for createPreliminaryContext if not given in the
constructor. For examples on its usage see "ContextBinding".
● It is used by the ContextBinding as a default value for usePreliminaryContext if not given in the
constructor. For examples on its usage see "ContextBinding"..
ODataListBinding v2
The constructor parameter is named usePreliminaryContext (type Boolean) and has the following properties:
● Default value is false, as it is derived from the ODataModel's default.
● If set to true:
○ The ODataListBinding accepts preliminary contexts (for example. in a setContext() call).
○ The ODataListBinding fires a change event with ChangeReason.Context, if the binding is updated
and a preliminary context was set.
ODataContextBinding
The ODataContextBinding supports two different parameters:
● usePreliminaryContext (same as a ODataListBinding v2)
● createPreliminaryContext
○ If the binding cannot be resolved, it still creates a preliminary binding context, which can be used by other
subordinate dependent bindings, which have set the usePreliminaryContext option to true.
○ A change event with ChangeReason.Context is fired once the data is loaded for the currently
preliminary Context instance. Afterwards, the existing Context instance is not considered "preliminary"
anymore.
Relationship Between Binding and Model Settings
Default Behavior
To describe the preliminary context feature in more detail, we first have to look at the default Model/Binding
behavior. Let's look at the simple example in the following graphic.
572
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
Figure 138: Simple Binding Example
Without using preliminary contexts, Binding 1 resolves only after Binding 0 is resolved.
For example, if Binding 1 is a relative ODataListBinding on a Table control, its OData request will only be
sent, once the data for the absolute Binding 0 is available, for example by using an Element binding on a Panel
control.
This leads to two subsequent OData requests, one for Binding 0 and afterwards one for Binding 1, as shown in
the following table:
Table 22: Simple Example: Binding Resolution
Request Number
Content
1
GET Products(1)
2
GET Products(1)/Supplier
Now let's look at a more complex example.
Figure 139: Complex Binding Example
In this example we add another binding, which will be resolved once Binding 0 and Binding 1 are resolved. This
leads to the following three individual $batch requests:
Table 23: Complex Example: Binding Resolution
Request Number
Content
1
GET Products(1)
2
GET Products(1)/Supplier
3
GET Suppliers(1)/Products
Optimized Behavior
Let's look at the same simple example but with some optimizations.
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
573
Figure 140: Simple Binding Example - Optimized
Here Binding 1 uses the preliminary context created by Binding 0, and thus the request URL can directly be
resolved.
This now leads to only a single $batch request:
Table 24: Simple Example: Binding Resolution Optimized
Request Number
Content
1
GET Products(1)
GET Products(1)/Supplier
In this example Binding 1 has set its usePreliminaryContext flag to true, and thus accepts preliminary
contexts to be set.
Note
If either createPreliminaryContext or usePreliminaryContext is set to false, the default behavior is
active.
Now let's see how this works in the complex example.
Figure 141: Complex Binding Example - Optimized
In this example we added another binding to the scenario. Binding 2 is again a relative binding, which can only
resolve once Binding 1 is resolved. Binding 1 behaves just as before.
574
OpenUI5: UI Development Toolkit for HTML5
OpenUI5: UI Development Toolkit for HTML5
In this case the single, generated request looks like this:
Table 25: Complex Example: Binding Resolution Optimized
Request Number
Content
1
GET Products(1)
GET Products(1)/Supplier
GET Products(1)/Supplier/Products
Results and Conclusion
Notice how the Products list of the Supplier is referenced through the entity Products(1).This is a result of
bundling all data requests into one single $batch request, without waiting for the Products(1) entity and its
associated Supplier entity to be loaded.
As opposed to the default behavior, we do not require to have the Products(1) and Supplier entities loaded
before sending the data request for the Supplier's Products.Supplier.So in this case we use a data path based
on Products(1) and not the ID of the Supplier. You can compare that to the default behavior of the complex
example described above.
Example
What would happen if one binding in the above chain does not set the usePreliminaryContext or the
createPreliminaryContext option to true?
F