Saphelp Ui
Saphelp Ui
Saphelp Ui
The documentation may have changed since you downloaded the PDF. You can always find the latest information on SAP Help
Portal.
Note
This PDF document contains the selected topic and its subtopics (max. 150) in the selected structure. Subtopics from other structures are not included.
The selected structure has more than 150 subtopics. This download contains only the first 150 subtopics. You can manually download the missing
subtopics.
2017 SAP SE or an SAP affiliate company. All rights reserved. No part of this publication may be reproduced or transmitted in any form or for any purpose
without the express permission of SAP SE. The information contained herein may be changed without prior notice. Some software products marketed by SAP
SE and its distributors contain proprietary software components of other software vendors. National product specifications may vary. These materials are
provided by SAP SE and its affiliated companies ("SAP Group") for informational purposes only, without representation or warranty of any kind, and SAP
Group shall not be liable for errors or omissions with respect to the materials. The only warranties for SAP Group products and services are those that are set
forth in the express warranty statements accompanying such products and services, if any. Nothing herein should be construed as constituting an additional
warranty. SAP and other SAP products and services mentioned herein as well as their respective logos are trademarks or registered trademarks of SAP SE in
Germany and other countries. Please see www.sap.com/corporate-en/legal/copyright/index.epx#trademark for additional trademark information and notices.
Table of content
SAPUI5 offers powerful development concepts: Disclaimer: The below video is not part of the SAP product
Built-in extensibility concepts at code and application level documentation. Please read the legal disclaimer for video links
Data binding types and Model-View-Controller (MVC) before viewing this video.
Feature-rich UI controls for handling complex UI patterns and predefined layouts for typical
use cases.
Full translation support
Keyboard interaction support and accessibility features
And many more....
SAP Fiori applications are built with SAPUI5. SAP Fiori apps follow the SAP Fiori Design Guidelines to ensure consistent design and a high level of design
quality. Explore the guidelines: https://experience.sap.com/fiori-design/.
For more information about SAP Fiori, see http://www.sap.com/fiori
SAPUI5 Highlights
Note
Check the SAPUI5 playlist in the SAP Technology YouTube channel for the latest highlights videos!
Disclaimer: The below video is not part of the SAP product documentation. Disclaimer: The below video is not part of the SAP product documentation.
Please read the legal disclaimer for video links before viewing this video. Please read the legal disclaimer for video links before viewing this video.
In this section:
1.1 What's New in SAPUI5 1.38
With this release the UI development toolkit for HTML5 (SAPUI5) is upgraded from version 1.36 to 1.38.
What's New in SAPUI5 1.36
With this release the UI development toolkit for HTML5 (SAPUI5) is upgraded from version 1.34 to 1.36.
What's New in SAPUI5 1.34
With this release the UI development toolkit for HTML5 (SAPUI5) is upgraded from version 1.32 to 1.34.
What's New in SAPUI5 1.32
Documentation Improvements
Thanks a lot to all of you who have used the Demo Kit feedback function! We have received lots of comments, many of which regarding our tutorials, and are
continuously improving the documentation based on your findings.
Please carry on giving us your feedback: even though we cannot update the documentation straight away, your feedback will be considered in the next
version!
New or reworked documentation chapters that are not mentioned in the following sections:
Chapter Routing and Navigation is now updated and reworked.
The Smart Controls tutorial now contains additional steps.
Deprecation
The following libraries are deprecated as of this version:
sap.ui.commons
sap.ui.ux3
sap.makit
The following themes are also deprecated as of this version:
sap_ux
sap_platinum
sap_goldreflection
For more information, see Deprecated Themes and Libraries.
New Features
We have 46 new icons, and some existing icons have been redesigned - check the Icon Explorer in the Demo Kit for details.
Multiple preprocessors for XML views
We have enhanced the XML view so that it is now capable of running more than one preprocessor per hook. Additionally, the new hook viewxml has
been introduced. For more information, see Preprocessing XML Views.
SAPUI5 OData V4 model
We are providing an initial version of the SAPUI5 OData V4 Model. This model supports the following:
Read access
Updating properties of OData entities via two-way-binding
Operation (function and action) execution
Grouping data requests in a batch request
Server-side sorting and filtering
Restriction
This is the first version of the SAPUI5 OData V4 model. Due to its limited feature scope, we recommend you do not use this release to develop
applications that are to be used in production systems. Please look at the detailed documentation of the features, as certain parts of a feature may
be missing which you might expect as given. While our intention was to be compatible with existing controls, existing controls might not work due to
small incompatibilities compared to sap.ui.model.odata.(v2.)ODataModel, or due to missing features in the model. Up to now, only limited
tests with controls have been done with the SAPUI5 OData V4 model. The interface for applications has been changed to make usage of the model
easier and more efficient. A summary of these changes is documented in the section Changes Compared to OData V2 Model.
New Controls
sap.m.ObjectMarker: The ObjectMarker control represents the status of an object with icon and/or text. It can be interactive (as a link) or non-
interactive. It has the following predefined types:
Flagged
Favorite
Draft
Locked
Unsaved
An object might have multiple ObjectMarkers at the same time but the editing states (Locked, Draft, and Unsaved) are mutually exclusive.
sap.m.RangeSlider is a new input control that is used to select a range of values. The RangeSlider has two slider handles that can be moved
along a predefined numerical range scale. This control extends the sap.m.Slider and introduces additional functionality.
sap.m.StepInput: The StepInput control allows the user to change the input value with a predefined step. The value can be changed using the
increment/decrement buttons or keys on the keyboard. On the desktop, when using the keyboard PgUp and PgDn keys, the value increases/decreases
two steps at a time.
sap.ui.comp.smartmicrohart.SmartMicroChart: This control is used to create different types of micro charts based on OData metadata. With
this control, you can define annotations once for different micro chart types. The SmartMicroChart control contains a wrapper that interprets the chart
type of the Chart annotation and delegates this information to the corresponding smart micro charts, SmartAreaMicroChart or
SmartBulletMicroChart control.
sap.ui.comp.smartmicrochart.SmartBulletMicroChart: The SmartBulletMicroChart control creates a
sap.suite.ui.microchart.BulletMicroChart based on OData metadata and the user-specific implementation.
sap.ui.comp.SmartMicroChart.SmartAreaMicroChart: The SmartAreaMicroChart control creates a
sap.suite.ui.microchart.AreaMicroChart based on OData metadata annotations and the user-specific configuration.
sap.ui.layout.ResponsiveSplitter is a layout control that is used to visually divide the content of its parent. The control is responsive and can
adjust its contents to any screen size. On smaller screens, pagination is used to allow navigation to all splitter panes.
sap.ui.vk.Notifications: The Notifications control allows you to add an icon in your application that displays the number of errors and
warnings that may have resulted from loading a 2D image or 3D file. The icon can be clicked to display more information about the errors and
warnings.
Improved Features
One page acceptance test (OPA):
The Press and EnterText actions now support a larger number of controls and can now be executed on embedded controls by specifying the control
suffix.
SAPUI5 application index: The index is now based on the layered repository and is updated automatically in most cases. For the other cases we
recommend that you schedule the update report every 30 minutes using the default mode (expiration period of 24 hours). For more information, see
SAPUI5 Application Index.
Improved Controls
sap.m.ComboBox:
Is now supported on mobile phones. The list of available values will open as a full-screen dialog on small devices.
The new loadItem event makes it possible to defer initialization of items in the ComboBox dropdown list control to a point in time when the items
are required. This helps to improve performance.
sap.m.DatePicker, sap.m.DateTimePicker, sap.m.PlanningCalendar, and sap.ui.unified.Calendar: You can now set minimum and
maximum dates to limit the range of available dates.
sap.m.GenericTile: The GenericTile control has a new responsive design that significantly improves the user experience, it has also been
optimized for larger smartphones. The GenericTile adjusts its size to fit all the different display sizes of the current devices supported by SAPUI5
(see Browser and Platform Support). The main changes are the tile size, font size, padding, the new ImageContent control, and new samples showing
the variety of use cases for the GenericTile.
sap.uxap.ObjectPageLayout:
It supports scrolling to a particular section, based on its ID. This allows easier access to all parts of the application and consistent navigation back
to a previous position within the ObjectPage.
Performance is improved for the use case with no Blocks. The ObjectPage now supports lazy loading with the stashed property of the
ObjectPageLazyLoader. As a result, you avoid the additional creation of XML views for each Block.
Smart Templates
General Features:
Actions for line items in tables
You can now enable certain types of actions within a line item column in a table in both the list report and object page views. Actions you can enable at
the line item level include those that trigger a back-end call through the OData service, for example, approve or unblock, or those that trigger navigation,
for example, to a different application.
Rating indicator in tables
You can now include a read-only rating indicator in the list report or in a table in the object page view. This allows you to visually represent the value of a
field with the corresponding number of stars (configurable). This field can indicate, for example, a rating or classification for a specific object or item.
Progress indicator in tables
You can now include a progress indicator in the list report or in a table in the object page view. This allows users to visualize the level of completion of,
for example, a project or a goal. Optionally, you can set up the following:
Whether a single color is used in the progress bar (default: blue), or multiple colors are used to indicate criticality (example: red to indicate that
stock levels are very low)
The value used to calculate the percentage value displayed
External navigation
You can now enable navigation from line items in a table to a different app using intent-based navigation. This feature is available for the list report and
tables in the object page view.
List Report View:
Deletion of one or more items
Users can now select and delete one or more items directly from the list report view.
Images in tables
You can now display images in table columns.
Harmonized variant management option
Variants created by the user for the smart filter bar and smart table can now be saved together using the variant for the smart filter bar.
Disabling editing status filter
You can disable the editing status filter for the list report.
Object Page View:
Image expansion
End users of your app can now click an image within the object page to see an expanded view of the image.
Plain text facet in object page header
You can now add a plain text facet to the header area of an object page. The plain text facet allows you to add a single field or block of text to the
Overview Pages
Overview pages have been enhanced with the following features:
Selection fields: With the UI.SelectionFields configuration in the annotations file, you can define the filter you want to add to the filter bar by
default.
Data points: Now, you can display as many data points as are present in the backend, in card charts. To limit the number of records, adjust the value of
the UI.PresentationVariant.MaxItems property in the annotion file (donut charts use all records, so this limitation isn't needed).
You can now create the following cards:
Data, such as total product sales over a period of A stacked column chart is similar to the column chart; With a vertical bullet chart, you can visualize a single
years, can be displayed in a column chart. The however, it visualizes multiple measures or measure and compare it to a defined reference
number of columns is equal to the number of dimensions by stacking the data on top of each other value, such as a target unit of measure. This target is
measures in the annotation file. in a column. displayed a solid black line.
Related Information
Upgrading
Deprecated Themes and Libraries
OData V4 Model
Changes Compared to OData V2 Model
Preprocessing XML Views
SAPUI5 Application Index
sap.ui.comp
Smart Controls Tutorial
Analytic Cards
New Features
The OData Model V2 now supports binding against function import parameters. If the function import returns result data, then this result data can also
be accessed and bound. For more information, see OData V2 Model.
You can use jQuery.sap.measure to measure the performance of your JavaScript code. You can retrieve the results via APIs. You can calculate
averages and use filters or categories. For more information, see Performance Measurement Using jQuery.sap.measure.
New Controls
Gantt charts are a state-of-the-art function that can be used in various application domains, particularly in planning and scheduling. In resource-based
planning, for instance, a Gantt chart can be used to show the plan using intuitive graphics, making it easier to view resource availability. In addition, it
also helps when monitoring the actual execution of the plan and enables interactive resource-based planning by drag&drop. In project management, it is
often used to display the project structure in a hierarchical way, showing milestones and relationships between activities.
The GanttChart control provides a rich set of features, such as displaying one or more Gantt charts, various events for user interaction (e.g.
drag&drop or mouse click events), multiple basic shapes (e.g. rectangles, milestones, triangles, relationships) and more sophisticated shapes (e.g.
utilization line chart, utilization bar chart) drawn in the chart area, comprehensive tree table features provided by the underlying TreeTable control, and
powerful configuration capabilities (e.g. shape colors and patterns, application-specific buttons) as well as zooming and scrolling. In addition, a
consuming application can implement its own shapes for the chart area. The Gantt chart control does not contain any business logic, meaning
consuming applications have to define their own user interaction and business semantics.
sap.gantt.GanttChartWithTable: The GanttChartWithTable control contains a tree table to the left and a chart to the right. It can
display a data hierarchy in the tree table and visualize its specific data along a time axis in the chart area.
All controls that can be used inside the tree table are also supported in the Gantt chart.
The application can control the visibility of the toolbar above the tree table, the display of buttons and menu items, as well as the time axis
display, such as planning period and zoom level. The application can also control the display of shapes contained within the chart area, such as
shape types and colors.
sap.gantt.GanttChartContainer: The GanttChartContainer control contains more than one GanttChartWithTable control and a
global toolbar. It can affect the behavior of all embedded GanttChartWithTable controls, such as zoom level, view layout, and default
settings.
The global toolbar provides default buttons, such as buttons for zooming, settings, and layout. The application can define additional buttons or hide
default buttons as needed. Multiple views can be laid out vertically or horizontally, and the scrolling (both horizontally and vertically) can be
synchronized, depending on the settings. Default options are provided in the settings to control the behavior of the control, such as Indicate
Current Time , Show Cursor Line , Show Divider Line , Synchronize Time Scroll , and Synchronize Row Scroll .
Different properties are provided allowing you to configure the percentage value. The control supports a responsive design. Depending on the available
space and favored size of the chart, you can influence the chart's size and representation. You can also use a property to define the color of the circle.
Improved Features
Performance improvements:
When possible, data requests will now be send out before rendering is triggered to improve overall end-to-end times.
Initial auto-expand to n levels is now supported in TreeTable for OData-based services with fewer requests based on a new OData annotation.
Relative time formatter for sap.ui.core.format.DateFormat: The new formatting options short and narrow for the relativeStyle allow a
concise display of a relative date like 3 years ago or 2 hours ago.
ABAP NUMC types are now supported in annotation processing and OData types as follows:
ABAP NUMC types can be represented in OData services as Edm.String with OData v2 SAP Extension 'sap:display-
format="NonNegative"' and constraint 'maxlength' set.
ODataMetaModel also provides the OData v2 SAP Extension 'sap:display-format="NonNegative"' as v4 annotation
'"com.sap.vocabularies.Common.v1.IsDigitSequence": { "Bool" : "true" }'. For more information, see Meta Model for
OData V2.
AnnotationHelper.format considers the 'com.sap.vocabularies.Common.v1.IsDigitSequence' annotation and 'maxlength'
constraint when creating a binding. For more information, see Annotation Helper.
sap.ui.model.odata.type.String reacts to the 'com.sap.vocabularies.Common.v1.IsDigitSequence' annotation and
'maxlength' constraint when processing its values.
Improved Controls
sap.m.FlexBox: The FlexBox control now allows wrapping items into multiple lines and influencing the layout of these lines. The sizing behavior of
flex items can be adjusted with minimum or maximum width and height values.
sap.m.MultiInput: This control now has the new property maxTokens, which defines the maximum number of tokens that are allowed within the
MultiInput.
sap.m.SearchField: This control now has a new suggestions feature. When a user enters something in the search field, the application can now
display a list of suggestions.
sap.m.TimePicker: A user can now edit or delete values directly from the input field on mobile devices.
sap.m.UploadCollection: You can now download an item from the upload collection list. For this feature, two new methods have been added, one
to the UploadCollection and the other to the UploadCollectionItem. This means you can choose from where you want to call the method. Both
methods allow you to define if the default download location of the browser is used or if the file dialog is called via a parameter to store to a different
location. For more information, see Upload Collection.
sap.m.Wizard: You can now add titles for steps (in addition to icons) that are shown in the progress navigator of a wizard. Titles help users to relate
the progress navigation part to the step content easily.
sap.ui.layout.form.SimpleForm: This control now has the new property backgroundDesign, which can be used to switch between different
backgrounds.
sap.ui.comp.smartchart.SmartChart: The SmartChart control now allows you to navigate to the related semantic object for your chart when
you select the relevant part of the chart, such as a column. For this purpose, the SmartLink control has been implemented along with a new button
( Jump To ) in the toolbar.
In addition, the control provides the following new features in the toolbar:
Chart type shown as icon
Zoom in and out
Maximize and minimize
Toggle legend visibility
All actions performed in the chart itself (rather than in Settings ) can now also be saved as variants.
sap.ui.comp.smartform.SmartForm: The adjustLabelSpan and singleContainerFullSize properties of SimpleForm have been added
to the SmartForm control.
For more information about smart controls, see sap.ui.comp.
sap.ui.vk library: The following improvements have been implemented in the visual interaction toolkit:
The NodeHierarchy control now has the new methods fineNodesByName and findNodesByMetadata, which allow you to search for
specific nodes in a 3D scene.
The NativeViewport control now allows for viewing a wider range of file formats that are natively-supported by browsers. For more information,
see Native Viewport.
App Templates
The app templates now have an option to generate apps that run standalone, independently of the SAP Fiori launchpad. When used in this way, apps get a
separate index.html file to start the app. For more information, see App Templates: Kick Start Your App Development and Folder Structure: Where to Put
Your Files.
Smart Templates
Extensibility: The metadata-driven smart templates provide a variety of screen configurations. To further increase flexibility for developers, we have
introduced extension points, where you can add your own views and controller extensions using a new API interface.
Easier navigation in object page: In child object pages, breadcrumb-like navigation allows easy access to parent object pages.
New default filter option for draft handling: In the list report, you have a new default filter option that allows you to filter by draft document status.
Subsection in sections: You can now create subsections within a section of an object page.
Overview Pages
Overview pages now support conditional formatting. For example, using the SAPUI5 formatter, cards in overview pages can now show 1K instead of
1000 .
You can now display numeric KPIs in the headers of all card types, not just analytic cards. The number displayed in a KPI header is derived from an
aggregation of values in the OData service, as defined in the manifest.json file. In addition, the header displays a title, the unit of measurement, the
trend, and percentage of change.
Tutorials
Mock Server: In this tutorial we'll explore some advanced features of the mock server. If no OData service is available or if you simply don't want to
depend on the OData back-end connectivity for your development and tests, the mock server can mimic the OData back-end calls: Mock Server.
Worklist App: In this tutorial we'll build a simple worklist app using the worklist app template. This app could be used by a shop owner to manage his
product stock levels, for example: Worklist App.
3D Viewer: In this tutorial you'll learn how to create applications with 3D viewing capabilities using the controls in the Visual Interaction toolkit
(sap.ui.vk library): 3D Viewer.
Walkthrough: The Walkthrough tutorial has been updated with some corrections based on user feedback. Many thanks to all readers who got in
touch with us! Steps 26 and 27 in particular have been completely rewritten: Walkthrough.
sap.m.MaskInput: The sap.m.MaskInput control is used to govern what a user is allowed to enter in an input field. The mask has a fixed length
format set upon instantiation and the user input has to conform to it. We recommend using this when the user enters text or numbers with specific
formatting, such as a phone number, ZIP code, credit card number, etc.
When creating a new mask, you can change the configuration of some default properties. For example, the default placeholder symbol _ can be changed
to something else. Note that the sap.m.MaskInput control extends sap.m.Input and has all the normal properties of an input field.
sap.m.NotificationListGroup: The NotificationListGroup is a control used to group a list of NotificationListItems. The group
consists of a header containing title, group's author picture, group's author name, time stamp and a priority stripe, a body containing a list of
notifications, and a footer containing a collapse/expand button, list of action buttons and a priority stripe. The sap.ui.core.Priority property
defines the priority of the group, which can either be defined by the developer or automatically calculated by the group (based on the highest priority of
its notifications).
sap.m.NotificationListItem: The NotificationListItem is a control used to display a notification, and it extends the ListItemBase
control. The notification consists of a title, description, author picture, author name, time stamp, priority, close button and a list of action buttons. All
these parts are both optional and configurable. The sap.ui.core.Priority property defines the priority of the notification and is visualized on the
left side of the container with a stripe displayed in a different color depending on the priority.
sap.m.TabContainer: The sap.m.TabContainer control is a generic tab container for managing multiple tabs.
The control contains a TabStrip area where the user can choose which tab to read or edit. Every tab has a close/decline button (visible on mouse
hover) that removes the item from the screen and an asterisk (*) indicator that notifies if there are unsaved changes in a particular tab. This can be
handled by the application developer. In order to navigate through many tabs, there are also left and right arrows. New tabs can be easily added using
the + button in the upper right corner.
Another way for users to find their tab is to look in the Select list where all the tabs from the TabStrip are listed.
GenericTile control moved to sap.m: With this release, the GenericTile, TileContent, FeedContent (formerly JamContent),
NewsContent, NumericContent, and SlideTile (formerly DynamicContainer) controls have been moved from the sap.suite.ui.commons
library to the sap.m library.
If you have already included one of these controls before SAPUI5 1.34, a wrapper ensures that the embedding still works for each control. To benefit
from all the enhancements or new features for one of these controls as of SAPUI5 1.34, you need to switch to the controls in the sap.m library. With
SAPUI5 1.34, all these controls in the sap.suite.ui.commons library are marked as deprecated.
During the move, the following controls have been renamed:
JamContent to FeedContent
DynamicContainer to SlideTile
For more information, see Generic Tile.
sap.ui.comp.smartchart.SmartChart: The SmartChart control can be used to create complex diagrams. You can select a chart type, such as
Further features are provided by using other smart controls in combination with the SmartChart control: You can save a chart as a variant using the
SmartVariantManagement control or make chart-specific personalization settings using the sap.m.P13nDialog control.
In the personalization dialog, a new Chart panel has been added. You can also sort the data in your chart or filter it based on the conditions you define.
New sap.suite.ui.microchart library: With the new MicroChart library (sap.suite.ui.microchart), all the available MicroChart
controls of the sap.suite.ui.commons library have been moved to their own library.
If you have already included a MicroChart control before SAPUI5 version 1.34, a wrapper ensures that the embedding still works for each control. To
benefit from all the enhancements or new features for the MicroChart controls as of SAPUI5 1.34, you need to switch to the controls in the new
library. With SAPUI5 1.34, all the MicroChart controls in the sap.suite.ui.commons library are marked as deprecated.
During the move, the following controls and their elements have been renamed:
MicroAreaChart to AreaMicroChart
ComparisonChart to ComparisonMicroChart
BulletChart to BulletMicroChart
sap.tnt.ToolPage: A layout control used to put together the parts of a basic tools app - ToolHeader, SideNavigation and content area.
The ToolPage control also checks the type of the device on which it is displayed and sets the expanded state of the SideNavigation. This
can be controlled by the developer using the sideExpanded property of the ToolPage.
Overview Page
For the overview page, we added the following features:
Personalization: End users can now change the order of cards on the overview page by dragging and dropping cards and they can define which cards
are displayed on the overview page using the new Manage Cards entry in the Options menu.
Accessibility: Overview pages now support the High Contrast Black theme, as well as other accessibility features such as keyboard navigation and
screen reader support.
Globalization: Support for right-to-left languages has been added.
Support of Japanese Emperor calendar in sap.m.DatePicker and sap.ui.unified.Calendar: In the Japanese Emperor Calendar, a new era
starts if there is a new emperor. Both sap.ui.unified.Calendar and sap.m.DatePicker now support the Japanese Emperor Calendar.
sap.m.MessagePopover: The MessagePopover control now supports automated extraction of the longTextURL if the user uses GET requests in
the back end and automatic binding to the default message model.
sap.m.OverflowToolbar: The OverflowToolbar control has been visually restyled and improved as follows:
The default button style is now represented in the OverflowToolbar popover as a transparent button.
The popover arrow has been removed in favor of just a subtle offset.
The popover now appears either on top of or below the "..." (See more) button. In addition, its right side (or respectively its left side in RTL mode)
is aligned with the button's edge.
sap.m.Page: The sap.m.Page control accessibility is now extended with page landmark support. These landmarks are used, for example, by
assistive technologies (like screen readers) to provide a meaningful page overview.
sap.m.TimePicker: The scroll values can now be scrolled with the mouse wheel on mouse hover.
Smart correction of the value is now available when the user input does not conform to the defined time format but still matches default fallback time
format. Example: If the user enters 12:-- -- in an empty TimePicker input field and presses Enter, the value becomes 12:00 PM automatically.
sap.m.UploadCollection: You can use a customizable toolbar instead of the aggregation toolbar in the UploadCollection control. To get the
customizable toolbar, the aggregation toolbar has to be set to the sap.m.OverflowToolbar control that can be filled with your preferred SAPUI5
controls. To configure the position of the Add ( + ) pushbutton, the sap.m.UploadCollectionToolbarPlaceholder type was implemented.
The new mode property with the following values has been implemented: None, SingleSelect, MultiSelect, SingleSelectLeft,
SingleSelectMaster. The mode property defines the selection mode of the UploadCollection control.
sap.suite.ui.microchart.BulletMicroChart: The available theme-specific background color transparent of the CommonBackgroundModeType type
is now supported for the scaleColor property.
Time in smart controls in sap.ui.comp library: >The SmartField, SmartFilterBar, and SmartTable controls now support the Edm.Time
OData type. The fields bound to OData properties of this type are represented by the sap.m.TimePicker control. In particular, the filter panel showing
the conditions in the SmartFilterBar control allows filtering for time types using the TimePicker control.
Filtering in smart controls in sap.ui.comp library: Support of Boolean filters has been improved by using texts as defined in the Boolean OData
Note
This property is considered for desktop only. On mobile devices, the header always needs to snap because of the limited space.
Edit Header button: You can now add an Edit Header button. This is not an action button but rather a special button that provides a unified
interface for applications using the ObjectPage to switch to edit mode.
Unsaved changes indicator has been added to the ObjectPageHeader.
The new markChanges property enables the icon shown in the image above. A callback is also provided to handle the icon's click event.
Note
markChanges is mutually exclusive with markLocked.
Importance for ObjectPageHeader actions: You can now set priorities (High, Medium, Low) to action buttons, similarly to how importance works
for Sections and Subsections. For example, to make an action button overflow first, mark it with low importance like this:
<ObjectPageHeaderActionButton icon="sap-icon://action" text="action" importance="Low"/>
Apart from the features described above, several visual enhancements to the ObjectPage have been added, such as child page indication, an
indication that a section has subsections, and improved AnchorBar navigation.
New Tutorials
Testing
In this tutorial, you learn how to test application functionality using the testing tools that are delivered with SAPUI5. While going through the 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: Testing
Smart Templates
App developers can use smart templates to create SAP Fiori applications based on OData services and annotations requiring no JavaScript UI coding. An app
based on smart templates uses predefined template views and controllers that are provided centrally, so no application-specific view instances are required.
The SAPUI5 runtime interprets metadata and annotations of the underlying OData service and creates the corresponding views for the SAP Fiori app at
startup.
The predefined view templates and controllers ensure UI design consistency across similar apps. Additionally, the metadata-driven development model
significantly reduces the amount of frontend code per app, so the developer can focus on the business logic.
Smart templates comprise templates for "List Report" and "Object Page".
For more information, see Developing Apps with Smart Templates.
New Libraries for Draft Handling and Smart Template Reuse Components
The new libraries sap.ui.generic.app and sap.ui.generic.template are now available. sap.ui.generic.template uses
sap.ui.generic.app and provides template reuse components, such as TemplateComponent, which creates and initializes smart template
components.
In particular, the following artifacts are available in sap.ui.generic.app:
sap.ui.generic.app.transaction.TransactionController
This class provides an API with reusable client logic for applications. The class is used for submitting changes, invoking actions and OData CRUD
operations in general, and triggering client-side validations. The TransactionController can be used by applications that are based on smart
templates and also by any SAP Fiori app or SAPUI5 user interface.
sap.ui.generic.app.transaction.DraftController
This class implements the transactional interaction patterns specified for OData services supporting draft documents and provides methods for draft-
specific actions. The TransactionController and DraftController classes and their related entities enable runtime draft handling for
applications.
sap.ui.generic.app.navigation.NavigationController
This class handles all navigation-related and routing-related tasks for the applications.
sap.ui.generic.app.AppComponent
This class interprets the configuration contained in the application descriptor.
Overview Pages
PUBLIC Page 23 of 244
2014 SAP SE or an SAP affiliate company. All rights reserved.
Overview Pages
An overview page is a SAP Fiori application, based on OData services and annotations, which provides an overview of a specific area and enables end users
to interact with business data and perform key actions.
The overview page application is comprised of the following:
Application header: provides a description of the area for which this application is providing an overview. The header also includes some general
application actions.
Card templates: a card is a smart component that uses UI annotation to render its content. Each card is bound to a single data source.
Global filter: provides the application-level filters that affect all cards in the overview page.
The sap.ovp library is now available with this version. This library provides template reuse components, such as the different card types and an application
template thatcreates and initializes the cards in the application.
App developers can create overview pages using the Overview Page wizard in SAP Web IDE.
For more information, see Overview Pages: Create Interactive Overviews of a Subject Area
UI5 Inspector
An open source Google Chrome Developer Tools extension that helps app developers to inspect, analyze and support SAPUI5-based apps. It offers the
following key features:
Inspect SAPUI5 controls and review their properties, bindings, and data model
Modify control properties on-the-fly and see how this affects the rendering and behavior
Find relevant framework information for SAPUI5-based apps
The UI5 Inspector can be downloaded as a standard extension from the Chrome Web Store . Once installed, it is then available in the Google Chrome
developer tools (by choosing F12) and becomes active when an SAPUI5 app is loaded.
New Controls
Visual Interaction toolkit (sap.ui.vk library):
The Visual Interaction toolkit (sap.ui.vk library) provides controls that enable the visualization and manipulation of 3D models in your browser. The
controls in the toolkit allow you to:
Specify the content resources to be loaded into the Viewer, how those resources are loaded, and the locations to load resources from
Display the tree of nodes from your model
Display procedures and steps in a 3D scene, and play these procedures and steps
More features are available in addition to the ones described in the preceding list. For a full list of controls in the sap.ui.vk library and a detailed
description about how to use each control, see the API Reference for sap.ui.vk in the Demo Kit.
You can easily get started with the Visual Interaction toolkit using the Viewer control - a composite control that connects and presents the essential
Visual Interaction toolkit controls. The Viewer control can be configured to specify which controls to display by default. The following screenshot shows
a simple Viewer application loaded with a 3D model. The application has been configured to display a Viewport, a SceneTree, and
StepNavigation.
In compact mode, the time-related fields can be easily and quickly filled by scrolling, tapping or using the arrows. A mask containing placeholder
symbols is integrated into the input field. This helps users to enter correct symbols and also validates them.
Improved Controls
Field Groups: Controls can now be assigned to logical groups which will trigger an event if this group is left.
Relative Date / Time Formatter: We now provide new options to provide relative date and times (e.g. seconds, minutes etc.)
A branching mode has been implemented to enable users to take multiple paths within a single wizard. Different outcomes and steps are displayed
depending on input from the previous steps. Dashed lines in the wizard progress navigator indicate that all subsequent steps may vary.
A new Summary page has been implemented that is displayed after all steps in a wizard are completed. This enables users to review their input
at a glance and edit it if needed.
Tutorials
This new and interactive learning format introduces you to all major development paradigms of SAPUI5. Each tutorial comes with a detailed documentation
and downloadable code in the Explored app in the Demo Kit. The tutorials include a Walkthrough that is starting from a simple 'Hello World' app and going
through the main topics in 35 steps. These topics include expression binding, mock servers, reuse dialogs, unit and integration tests, routing and navigation,
to name just a few. Other tutorials cover topics data binding, navigation and routing, using smart controls. For more information, see Tutorials.
App Templates
Two new app templates are now available for Master-Detail and Worklist apps. They are built using the latest best practises and as such can be used as a
starting point for developing apps with a standard setup. These app templates include basic features that can be extended with custom functionality to build a
full-blown app. You can also use both templates in SAP Web IDE.
Master-Detail: The classic screen layout based on the responsive split container, listing a set of objects and showing details for a selected object. You
can find a demo app for this in the Demo Kit under Demo Apps Master-Detail .
Worklist: This application shows a full-screen layout (in contrast to the Master-Detail layout), displaying a list of work items on the first page and more
details on a subpage. You can find a demo app for this in the Demo Kit under Demo Apps Worklist .
Documentation Improvements
The documentation has been restructured to allow easier navigation through all topics, most of the chapters have been reworked and updated.
The section formerly known as Installing SAPUI5 has been renamed and reworked. It has been updated with new information covering the most
common and recommended development environments. See Development Environment.
New Features
Screen Reader Support: Screen reader support based on the ARIA standard has been added for all sap.m controls, among others. The list of all
relevant libraries as well as important aspects related to control development are available in Screen Reader Support for SAPUI5 Controls. The important
aspects related to app development are available under Accessibility.
Islamic Calendar: The Islamic calendar has been added to the available calendar types and is available for the Calendar control. When, for example,
the current locale is en_US, but the calendar type is set to Islamic (either globally or on a data binding type or in a date formatter), the date May 6,
2015 is formatted as: Raj. 17, 1436 AH.
Switching the SAPUI5 version: You can now switch between existing installations of SAPUI5, for example for debugging purposes or compatibility
checks. If you open the debug mode in the debugging pop-up using Ctrl + Alt + Shift + P or the debugging panel using Ctrl + Alt + Shift + S , you can
choose from all existing installations or specify a custom URL. During the next startup, SAPUI5 will then be loaded from there - without modifying the
app.
Note
This will only work when the SAPUI5 core is loaded with the standard bootstrapping, it is not supported for SAP Fiori launchpad or cases where
customized bootstraps are used.
You can now use the Runtime Adaptation library to allow key users to adapt the user interface of apps during runtime, for example, by adding,
removing, or moving fields and groups. For more information, see UI Adaptation at Runtime.
New Controls
sap.m.semantic.SemanticPage: SemanticPage is an enhanced sap.m.Page control. It provides support for content with semantic meaning (for
example sap.m.semantic.AddAction, sap.m.semantic.EditAction and sap.m.semantic.SortAction) that has predefined visualization
properties and positioning in the page.
The following new controls are based on the SemanticPage control:
sap.m.semantic.MasterPage
sap.m.semantic.DetailPage
sap.m.semantic.FullscreenPage
sap.m.QuickView: The aim of the QuickView control is to display content in the form of a business card. It uses the
sap.m.ResponsivePopover control, meaning the content occupies the entire screen when using a mobile phone, whereas a popover is displayed on
other devices.
sap.m.QuickViewCard: This control represents a card that can be part of sap.m.QuickView and usually contains information about one person or
object. It allows this object to be linked to another object using one of the links in the card.
sap.m.MessageStrip: A simple control for showing messages as text with semantic color, an icon depending on the message type, and optionally a
link (leading to additional information).
sap.ui.layout.DynamicSideContent: The DynamicSideContent control allows additional (side) content to be displayed alongside or below the
main content, within the container in which the control is used. It is intended to be used within the sap.m.Page control but it can exist in any container
control, either taking the whole width of the screen or a large part of it.
sap.m.Wizard: The Wizard control is used to break down complex tasks into smaller steps, for example, creation of a business object or a long
registration form. It is a container control and has WizardSteps as content aggregations.
sap.uxap.ObjectPageLayout: An object page is a concept for a Web page, representing detailed information about a business object. An object
page has a header (sap.uxap.ObjectPageHeaderContent and sap.uxap.ObjectPageHeaderTitle), a navigation bar
(sap.uxap.AnchorBar) and is divided into sections (ObjectPageSection) and subsections (ObjectPageSubSection), each showing some
information about the object in question. For more information, see Object Page Layout.
Improved Controls
sap.m.Dialog: Two new public properties have been added: resizable and draggable. The default values of these properties are set to false.
sap.m.MessagePopover: The property markupDescription has been added, which enables the usage of HTML markup as message content.
sap.m.MultiInput and sap.m.MultiComboBox now support copy and paste from Microsoft Excel or Notepad.
sap.m.OverflowToolbar: You can now move more controls to the overflow area.
sap.m.Page: setBusy(true) now covers header and footer correctly, the contentOnlyBusy property has been added to leave them uncovered
(as was previously the case, mistakenly), for example, when the user is expected to keep typing text into a SearchField in the subheader while data
is being updated. The scrollToElement method for scrolling to a specific child element has been added.
sap.m.Panel: The property backgroundDesign has been added.
sap.m.ScrollContainer: The scrollToElement method for scrolling to a specific child element has been added.
sap.m.TextArea: The valueLiveUpdate property has been added. The value property of sap.m.TextArea is not updated on every keystroke,
but only when the user presses Enter or leaves the input field. The change was necessary to fully support the standard data binding with formatters and
types. If you still need an immediate update, you have two options: handle liveChange events or enable the boolean property valueLiveUpdate.
sap.m.UploadCollection:
The numberOfAttachmentsText property for overwriting the toolbar text and the 'Upload Pending' scenario have been implemented.
The numberOfAttachmentsText property enables applications to overwrite the toolbar text. It sets the title text in the toolbar of the list of
attachments. The default value is set to language-dependent Attachments (n) . The getItems().length method lets you also retrieve
the number of attachments in brackets. If a new title is set, the default is deactivated.
The new 'Upload Pending' scenario is provided in addition to the existing UploadCollection scenario (which is also known as an 'Instant
Upload'). In this new scenario, you first select the attachments and then upload them in a separate step. To decide which scenario to use,
you can select the instantUpload property with the respective value:
true for the 'Instant Upload' scenario
false for the 'Upload Pending' scenario
Up to now, only the selection of a single file is supported in this scenario (multiple property = false ). If required, you can select further
files and add them to the list.
After making the selection, you need to trigger the upload of the selected files. To do so, call the upload method of the
sap.m.UploadCollection control, for example by using a pushbutton in the application.
An application can now set file-specific header parameters, for example, file name. For the selection of multiple files in the 'Upload Pending'
scenario, the application can call the upload method to trigger the beforeUploadStarts event for each selected file.
sap.m.UploadCollection.Item: In this class, two new aggregations have been added: attributes (type sap.m.ObjectAttribute) and
statuses (type sap.m.ObjectStatus). These aggregations let you display any number of attributes and statuses for each uploaded item.
The contributor, fileSize, and uploadedDate properties have been deprecated accordingly and will be replaced by the additional attributes.
Note
Please remember that if one of these deprecated properties is filled in addition to the attribute with the same name, two attributes with the same title
are displayed, as these properties are also displayed as attributes. To make sure that the title does not appear twice, check whether one of the
deprecated properties is filled in addition to the attributes.
sap.m.ViewSettingsDialog: You can now define custom tabs with their content added to the dialog, along with three predefined tabs for sorting,
filtering and grouping.
sap.suite.ui.commons.MicroAreaChart: You can now display multiple lines. Each line can have one of the semantic colors or chart colors.
sap.suite.ui.commons.BulletChart: Visual enhancements to the thresholds have been added. Now thresholds are also visible above the actual
bar.
New Features
All of these features can be used on their own or in combination with each other. For more information, see the sap.ui.core.mvc.XMLView examples in
the Explored section of the Demo Kit.
Expression Binding: This new feature allows you to use expressions in bindings. This reduces the need to program formatters for views, especially when
using XML templating. For more information, see Expression Binding.
<!--Text for amount level using language-dependent texts from the resource model.-->
text="{= ${/amount} > 10000 ? ${i18n>/high} : ${i18n>/normal} }"
<!--Set to visible if the rating is VIP, ignoring case or if the order amount is greater than 10,000.-->
visible="{= ${/rating}.toUpperCase() === 'VIP' || ${/orderAmount} > 10000 }"
<!--Set to visible if the rating contains VIP, ignoring the case. -->
visible={= RegExp('vip', 'i').test(${/rating}) }
Composite Types: This new feature allows types to operate on composite bindings. The new Currency type, for example, uses this technology.
Message Handling: New message manager and message model, including a new message popover control.
Standard Margins & Container Paddings: You can now use predefined CSS classes to manage the margins and container paddings in your application.
Take a look at the following documentation and sample links to learn more:
Standard Margin Classes
Samples of standard margin classes in the Explored section of the Demo Kit
Standard Container Padding Classes
Samples of standard container padding classes in the Explored section of the Demo Kit
Routing: The following improvements have been made:
The following new classes have been created for routing:
sap.ui.core.routing.Targets
sap.ui.core.routing.Views
sap.ui.core.routing.Target
sap.m.routing.Targets
sap.ui.core.routing.Router
sap.ui.core.routing.TargetHandler
Views can now be displayed without modifying the hash with Targets.
Multiple views can now be displayed with one route using Targets.
Routing now consists of more modules, for example, the view creation and view placement have been separated from the router.
Some APIs for routes have been renamed and moved to Targets.
Class sap.m.routing.RouteMatchedHandler is now deprecated.
The improved routing is fully backward compatible with routing configurations, meaning you can perform step-by-step migrations.
Clickjacking Protection: In order to protect applications against clickjacking attacks, there are now settings available to control circumstances under which
an SAPUI5 application is allowed to run inside frames. For more information, see Frame Options and Whitelist Service.
Handlebars: The included Handlebars library has been upgraded to version 2.0.
New Controls
sap.m.MessagePage: This control is primarily designed to be used when a page is empty because no data is available, but can be used for other full-page
messages as well.
Figure 1: MessagePage
sap.m.MessagePopover: This control provides a popover containing a summarized list with messages that can be consumed by the user through several
different features like filtering and drilling down to the details.
sap.m.OverflowToolbar: This control provides you with the option to move buttons that do not fit in the visible area to an overflow menu. The
OverflowToolbar also provides a new layout data called sap.m.OverflowToolbarLayoutData that can alter the default behavior of controls by
forcing them to always stay in the overflow menu, or to never move in there. Currently, only controls of type sap.m.Button can be moved to the overflow
menu.
Figure 3: OverflowToolbar
sap.m.Title: This new control is a simple line of text which should be used for section headings. It provides semantic information about the level in the
page structure of the section the heading belongs to, and is needed for usability purposes.
sap.suite.ui.commons.HarveyBallMicroChart: This new control allows you to display a fraction value versus total as a micro chart. You can set
one of the predefined sizes and also optionally set the width of the control. You can use either semantic or palette colors for the fraction.
sap.suite.ui.commons.DeltaMicroChart: This new control allows you to display two values as well as an automatically calculated delta as a micro
chart. You can set one of the predefined sizes and also optionally set the width of the control. Semantic colors can be applied to the delta. You can define
custom labels for the values in the chart.
sap.ui.comp.navpopover.SmartLink: This new control provides a popover with navigation links to related applications, for example, more detailed
information about customer data.
sap.ui.comp.smartform.SmartForm: This new control makes it possible to render a form using OData metadata annotations. Depending on user
authorizations, the form enables users to switch from display to edit mode, add and group fields, rename field labels, and implement a user input check, for
example.
SAPUI5 Flexibility Services have been enhanced with features that allow you to adapt screens for multiple users using the SmartForm control, for example,
by adding and grouping fields.
sap.ui.comp.smartfield.SmartField: This new control offers a wrapper for other controls using OData metadata to determine which control has to be
instantiated.
Control Improvements
sap.m.Column: A new WithoutHeader popin display option has been added, which allows you to hide headers if needed.
sap.m.MessageBox: New options and features added, such as initialFocus, textDirection, verticalScrolling, horizontalScrolling,
and details.
sap.m.MultiInput: A new enableMultiLineMode API has been introduced, which displays the MultiInput control in multi-line display mode when
set to true.
sap.m.ObjectHeader: The responsive ObjectHeader has a new design depending on the device, which is based on media breakpoints. In the new
design, the sap.m.IconTabBar control is always displayed below the ObjectHeader.
sap.suite.ui.commons.ColumnMicroChart: You are now able to define labels and show them using semantic colors in the corners of the chart.
sap.ui.unified.Calendar: A multi-calendar view is now available, allowing you to place several calendars within a single view to support date picks
across months without navigation.
sap.ui.layout.form.SimpleForm: A new width property has now been added.
sap.ui.unified.FileUploader: Performing an upload with multiple: true and useMultipart: false is handled by several requests, one for
each file.
sap.ui.table.TreeTable: New paging support with lazy loading of tree nodes using OData hierarchy annotations and new auto expand feature which
expands the tree to a specified level. Export to Microsoft Excel also enabled.
sap.ui.table.Table: An auto BusyIndicator has now been added.
sap.suite.ui.commons.ProcessFlow: Mobile enablement has now been implemented for this control. As part of this enablement, the responsiveness
has been adapted. The initial zoom level is now responsive and depends on the container size of the ProcessFlow control.
P13nColumnsPanel: The new visibleItemsTreshold property in the P13nColumnsPanel as part of the P13nDialog control limits the number of
table columns selected by the end user during table personalization to avoid slow performance.
SAPUI5 Tools
The HTTP handler for SAPUI5 applications now supports cache busting at application or component level (report /UI5/APP_INDEX_CALCULATE needs to be
scheduled). The SAP Fiori launchpad uses this cache busting to ensure that the users do not have to clear the browser cache manually after application
updates on the server. For more information, see SAP Library for User Interface Add-On 1.0 for SAP NetWeaver on SAP Help Portal at
http://help.sap.com/nw-uiaddon. Under Application Help, open SAP Library and search for Cache Buster for SAP Fiori.
To study the coding and the effect of cache busting for application resources, now the sample application SIMPLETEST is available.
Documentation Improvements
Documentation of Coding Issues to Avoid: This new documentation section lists some of the most important issues that must be avoided when creating
applications using SAPUI5 (see Coding Issues to Avoid).
Demo Kit: The following improvements have been made to the Explored section:
You can now toggle the samples to full-screen display.
You can now open the samples in a new browser tab.
Most of the samples can now be downloaded as standalone, fully runnable web applications.
Usability Enhancements - Keyboard Shortcuts: Version 1.26 contains enhanced usability features, including new keyboard shortcuts for SAPUI5 controls.
Usability Enhancements - High Contrast Black: Version 1.26 also includes High Contrast Black (HCB) theme support for the sap.m library.
Fast Keyboard Navigation: The new fast keyboard navigation allows you to navigate quickly between groups using F6. For more information, see Defining
Groups for Fast Navigation (F6).
Mobile Diagnostics: We have improved the structure of the mobile diagnostics dialog and added a new function for end-to-end (E2E) tracing. You can now
open the dialog with the following touch combination: press two fingers on a non-interactive screen area (for example, a blank area) for at least 3 seconds, then
tap with a third finger while still holding the other two on the screen. For more information, see Technical Information Dialog.
jQuery 1.11.1: This version of jQuery is now used by default.
Extensibility: ExtensionPoints are now also offered in JSViews and controls can be hidden in all View types.
OPA (One Page Acceptance) Tests: The OPA tests have been improved. There is now a new way to structure your OPA tests: If you have multiple pages or
UI areas that have several operations, you should place them as reuse functionality in page objects to make your OPA tests easier to maintain.
Visible Attribute: A visible attribute is now available for all controls, as they now inherit this property from the sap.ui.core.Control class.
SAPUI5 Flexibility Services: These services offer a multiple-layer approach and provide various features with regards to configuration and personalization
functions for your SAPUI5 applications, such as variant management and personalized table settings.
New Node Type for sap.suite.ui.commons.ProcessFlow: The node type Planned with Issue is now available for
sap.suite.ui.commons.ProcessFlow, meaning you can now choose between using a node Planned without a status, or with the status Planned with
Issue .
sap.m.RadioButtonGroup: This new control improves the use of layouting, keyboard navigation and data binding for radio buttons.
sap.m.SelectList:
sap.m.UploadCollection: This new control is a list control for attachment management. It is based on the sap.ui.unified.FileUploader control
and allows you to upload, edit or delete attachments.
Smart Table: This new control is used to create various types of tables based on OData metadata and for personalized table settings.
In this section:
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.
Versioning of SAPUI5
As of SAPUI5 1.6 there is a close coupling of SAPUI5 core/runtime and SAPUI5 tools: It is required that within a running system both have the same
version, that is, have the same major and minor version.
Upgrading
The following sections describe what you have to consider when upgrading to a new version of SAPUI5.
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 SAPUI5.
Supported Library Combinations
SAPUI5 provides a set of JavaScript and CSS libraries, which can be combined in an application using the combinations that are supported.
Deprecated Themes and Libraries
As SAPUI5 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.
Browser and Platform Support
This sections describes which browsers and platforms are supported by the various SAPUI5 libraries.
Demo Kit and Platform-Specific Documentation
The SAPUI5 documentation is included in the Demo Kit. The Demo Kit also provides you with technical documentation and samples. There are also
platform-specific variants of the documentation available at other locations.
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 (SAPUI5) development environment.
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 in the following products:
SAP HANA
SAP HANA Cloud Platform
SAP NetWeaver 7.4 or higher (included in the UI technologies (SAP_UI) component)
User interface add-on for SAP NetWeaver 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, and the more desktop-focused libraries sap.ui.commons, sap.ui.ux3.)
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 HANA 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
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/
Versioning of SAPUI5
As of SAPUI5 1.6 there is a close coupling of SAPUI5 core/runtime and SAPUI5 tools: It is required that within a running system both have the same version,
that is, have the same major and minor version.
For example, SAPUI5 core/runtime 1.x.1 works together with SAPUI5 tools 1.x.2, but not with SAPUI5 tools 1.y. For a future release it is planned to have a
more loose coupling here with regards to the minor and micro version.
Note
As of SAPUI5 1.6 we can only support customers who follow this rule in their system landscapes.
Versioning Scheme
SAPUI5 uses a 3-digit version identifier, for example 1.26.4. These digits have the following meaning:
The first digit (1) specifies the major version number.
The second digit (26) specifies the minor version number.
The third digit (4) specifies the patch version number.
The version number for each new SAPUI5 release contains a major and minor version number. The patch version number is only used to identify patches
within a release.
1.2.3 Upgrading
The following sections describe what you have to consider when upgrading to a new version of SAPUI5.
In this section:
Upgrading from a Version Below 1.38
When upgrading to the current SAPUI5 version from a version below 1.38 (released in June 2016), check whether the changes listed below influence your
apps.
Upgrading from a Version Below 1.30
When upgrading to the current SAPUI5 version from a version below 1.30 (released in September 2015), check whether the changes listed below influence
your apps.
Upgrading from a Version Below 1.20
When upgrading to the current SAPUI5 version from a version below 1.20 (released in April 2014), check whether the changes listed below influence your
apps.
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 SAPUI5 unit tests used this approach to simulate mouse or key events.
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 SAPUI5
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.
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.
Note
If you use additional open-source libraries that depend on jQuery, check whether they need to be upgraded as well.
jQuery: Dependency onjQuery Code that is Specific to Microsoft Internet Explorer 8.0 (IE8)
SAPUI5 contained some code that referenced IE8-specific members of the jQuery API (like jquery.css.Hooks.opacity.set). This code has been
removed.
try {
jQuery.ajax({
url: './nonexisting.json',
async: false,
dataType: 'json',
success: function() {
console.info("success");
},
error: function(xhr,error,status) {
^// this error will not be caught by the try/catch below, only by the global error handler above
throw new Error("error fetching resource with jQuery " + jQuery.fn.jquery + ": " + xhr.status + " " + status);
}
});
} catch (e) {
// never reached with jQuery 2, but worked with jQuery 1.11.1
alert("try-catch caught " + e.message);
}
This means that when jQuery is loaded before Sinon, it will keep a reference to the original Date.now function in the jQuery.now API. Therefore any code
that uses jQuery.now() will not work with Sinon fake timers, or more specific, it will not react on e.g. sinon.timer.tick(xyz).
This especially affects jQuery animations and sap.ui.core.Popup that uses these animations. They won't work with sinon.fakeTimers out of the box.
To solve this, you can stub the jQuery.now() function when Sinon is used. Stub it in a way that the mocked version of Date.now is called.
Solution
As short-term solution, you can use module sap/ui/test/qunit-junit.js to wrap an existing 'qunit-header' in case id='qunit' is missing.
As long-term solution, you have to migrate test pages to the suggested page content.
Solution
Remove unnecessary start() and stop() calls.
Solution
As short-term solution, you can use module sap/ui/test/qunit-junit.js to restore the old behavior.
Related Information
Cookbook for QUnit on http://qunitjs.com/
Note
If you use additional open-source libraries that depend on jQuery, check whether they need to be upgraded as well.
Also, the file names for jQuery UI effects have changed. Applications using the jQuery UI effects may also have to adopt their logic. In IE8 and jQuery version
<1.8, the detection whether the new jQueryUi logic is active or not fails. This may be due to interaction issues between jQuery UI version 1.10 and jQuery
version 1.7. In this case, the new jQuery UI position logic is used: Applications that still want to use jQuery version 1.7.1 and jQuery UI version 1.8 or older
have to load the jQueryUI core and not only the position module.
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.
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 , which is included in the SAPUI5 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 in the
Demo Kit.
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.
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)
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
Inheritance
Inheriting from SAPUI5 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 SAPUI5 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);
We recommend that you test inherited classes very carefully after updating SAPUI5 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 SAPUI5 version are flagged as "experimental". These experimental features and controls are not part of
the released scope of the delivered SAPUI5 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.
Note
You are only allowed to use closed source libraries for the SAPUI5 controls for which they are intended.
For a list of the third-party open source software used in SAPUI5, see the Included Third-Party Software link in the About dialog in the Demo Kit.
Related Information
Versioning of SAPUI5
Deprecated Themes
sap_ux
The sap_ux theme is deprecated as of version 1.38. This was one of the very first SAPUI5 themes and is only implemented by a small subset of the
sap.ui.commons and sap.ui.ux3 controls, which are also deprecated.
sap_platinum
The sap_platinum theme is deprecated as of version 1.38. This was one of the very first SAPUI5 themes and is only implemented by a small subset of
the sap.ui.commons and sap.ui.ux3 controls, which are also deprecated.
sap_goldreflection
The sap_goldreflection theme is deprecated as of version 1.38. This was one of the first SAPUI5 themes and is only implemented by the
sap.ui.commons and sap.ui.ux3 controls, which are also deprecated.
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 SAPUI5. 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 sap.m,
sap.ui.table, sap.uxap, sap.tnt. 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 was then added to SAPUI5, 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 Tree, Accordion and Row Repeater have been abandoned completely.
Note
Should you decide to ignore the deprecation and continue using sap.ui.commons , you should at least use the sap_bluecrystal theme and not the
sap_ux , sap_platinum and sap_goldreflection themes, which are also deprecated, as this is more favorable than using a combination of
library and theme that are both deprecated.
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 SAPs 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:
Thing Inspector Indirectly replaced by a different design for displaying object data.
Quick Views Concept abandoned as the concept of hovering with the mouse pointer over a
control does not exist on mobile devices.
For more information about the SAP Fiori design, see the SAP Fiori Design Guidelines.
Note
Should you decide to ignore the deprecation and continue using sap.ui.ux3 , you should at least use the sap_bluecrystal theme and not the
sap_ux , sap_platinum and sap_goldreflection themes, which are also deprecated, as this is more favorable than using a combination of
library and theme that are both deprecated.
sap.ca
The sap.ca library is deprecated as of version 1.22.
This library contains a mixture of controls for various use cases. Some were replaced by sap.m (e.g. DatePicker, Message) and some were discontinued
without being replaced (such as Hierarchy or OverflowContainer).
sap.me
The sap.me library is deprecated as of version 1.34.
The main feature within the sap.me library are the calendar controls. You find replacement controls in the sap.ui.unified library covering most but not
all features of the sap.me calendars.
sap.makit
The sap.makit library is deprecated as of version 1.38.
The sap.makit library contains a few chart controls that only support mobile platforms and do not support accessibility measures. This library is replaced
by sap.viz .
Related Information
Supported Library Combinations
Caution
The single source of truth about supported browsers and platforms is the Product Availability Matrix (PAM) that you can find at
https://support.sap.com/pam. SAPUI5 is not a product of its own, so please check the PAM for the product you're using SAPUI5 with. For more
information, see SAP Note 1716423 .
The following sections only contain additional information on restrictions and platform support information for specific SAPUI5 libraries in a summarized
form.
As SAPUI5 is based on CSS3, HTML5, and the new JavaScript API, only browsers with HTML5 capabilities are supported.
Depending on the platform your SAPUI5 applications 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 application.
Caution
Microsoft Internet Explorer 11 (IE11) Enterprise Mode is not supported.
In this section:
sap.ui.core, sap.ui.layout, sap.ui.unified
Basic libraries, supporting all platforms or browsers that are supported by any of the other libraries
sap.m, sap.ui.table, sap.uxap, sap.tnt
Browser support for sap.m, sap.ui.table, sap.uxap, and sap.tnt libraries on iOS, Android, MacOS, and Windows platforms.
sap.ui.comp
Browser support for sap.ui.comp library on MacOS and Windows platforms
sap.viz
Browser support for the sap.viz library
sap.ui.vbm
Browser support for sap.ui.vbm library on iOS, Android, MacOS, and Windows platforms
sap.ui.vk
Browser support for sap.ui.vk library on iOS, Mac OS, and Windows platforms.
sap.suite.ui.commons
Overview of the browser support for sap.suite.ui.commons library on iOS, Android, Mac OS X, and Windows platforms.
sap.suite.ui.microchart
Overview of the browser support for sap.suite.ui.microchart library on iOS, Android, Mac OS X, and Windows platforms.
sap.ushell
Browser support for sap.ushell library on iOS, Android, BlackBerry, MacOS, and Windows platforms
The following tables give an overview of the platforms supported by the sap.m, sap.ui.table, sap.uxap, and sap.tnt libraries using SAP Blue Crystal
theme.
Note
In general, only major versions that are also supported by the respective platform are supported.
For mobile operating systems, support is restricted to reference devices. For more information, see Device-Specific Policies.
iOS
iOS is supported as of platform version 8.
Browser Support
Safari Yes
Opera No
Android
Android is supported as of platform version 4.4.
Browser Support
Web View No
Opera No
Opera mini No
Firefox No
Mac OS
Mac OS X is supported as of version 10.10.
Windows
Touch is supported as of Windows 8.
Safari None
Opera None
* In regards to handling touch events, there are some issues with Windows 8.
Windows 10 Mobile
Browser Supported Version
Opera None
Exceptions
Caution
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.
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.
In this section:
Device-Specific Policies
For mobile operating systems, support is restricted to reference devices.
Visual Degradations
Depending on the combination of device and browser, visual degradations may occur in the sap.m library.
Related Information
Windows 8 Support - Known Issues
Apple
Apple always supports the 2 latest releases of the iOS operating system. SAPUI5 supports Apple mobile devices 3 years from the vendor device release date,
except defined otherwise. The following devices are supported:
Android
Android OS based devices are very fragmented in matters of operating system variants and hardware diversity. SAPUI5 supports the following Android
reference devices until 3 years from vendor device release date:
Windows Phone
SAPUI5 supports the following Windows Phone reference device until 3 years from vendor device release date:
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
Note
If you focus in the field, the placeholder disappears from view. If you leave the
focus without typing anything, the placeholder is then displayed again.
Mozilla Firefox Mozilla Firefox currently supports the native placeholder property and displays the
ellipsis correctly, indicating that the placeholder text stretches beyond the field that
is currently visible
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.
Browser Situation
Google Chrome Google Chrome supports the native placeholder property and displays multiple
lines along with a scrollbar
Internet Explorer Version 11 This version does not use the native placeholder property, instead 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 perspective, 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
placeholder 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
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 SAPUI5 is not used at all, as shown in the examples below.
Problem Description
When the text entered inside a text box is long and does not fit into the fixed
When choosing Select All to copy the contents of a text box on an iOS device,
the selection marked in blue continues outside the boundaries of the visible text
area, as shown in the graphic.
1.2.7.3 sap.ui.comp
Browser support for sap.ui.comp library on MacOS and Windows platforms
The following tables give an overview of the platforms supported by the sap.ui.comp library using SAP Blue Crystal theme.
Mac OS
On Mac OS X, Safari browser is supported in version 8 or higher.
Windows
Touch is supported as of Windows 8.
Safari None
Opera None
Caution
Windows Mobile is not supported.
1.2.7.4 sap.viz
1.2.7.5 sap.ui.vbm
Browser support for sap.ui.vbm library on iOS, Android, MacOS, and Windows platforms
iOS
iOs is supported as of platform version 7.
Browser Supported
Safari Yes
Web View No
Google Chrome No
Opera No
Android
Android is supported as of platform version 4.0
Browser Supported
Web View No
Opera No
Opera mini No
Firefox No
Mac OS
On Mac OS, Safari browser is supported in version 5.1 or higher.
Windows
Windows Desktop
Browser Supported
Mozilla Firefox No
Safari No
Opera No
Windows Tablet
For Windows Tablets only Internet Explorer is supported in the following versions:
Version Supported
10 Desktop Yes
11 Desktop Yes
Caution
Windows Mobile is not supported.
1.2.7.6 sap.ui.vk
Browser support for sap.ui.vk library on iOS, Mac OS, and Windows platforms.
sap.ui.vk is the Visual Interaction toolkit in SAPUI5. The following tables give an overview of the platforms and browsers supported by the sap.ui.vk library.
iOS
iOS is supported as of platform version 8.
On iOS, Safari browser is supported in versions 8 or higher.
Mac OS
Mac OS is supported as of platform version 10.10.
On Mac OS, Safari browser is supported in versions 8 or higher.
Windows
Windows Desktop
Windows is supported as of platform version 7.
Browser Supported
Note
The sap.ui.vk library is not supported for Internet Explorer 11 on Windows 8.0.
Windows Tablet
Windows is supported in platform version 8.1 only.
On Windows tablets, Internet Explorer is supported in version 11 only.
1.2.7.7 sap.suite.ui.commons
Overview of the browser support for sap.suite.ui.commons library on iOS, Android, Mac OS X, and Windows platforms.
The following tables give an overview of the platforms supported by the sap.suite.ui.commons library using SAP Blue Crystal theme.
iOS
iOs is supported as of platform version 8.4.1.
Browser Supported
Safari Yes
Web View No
Google Chrome No
Opera No
Android
Android is supported as of platform version 4.4.2.
Browser Supported
Opera No
Opera mini No
Firefox Yes
Mac OS
Browser Supported
Safari Yes
Mozilla Firefox No
Opera No
Windows
Note
Windows Touch is supported as of Windows 8 and Internet Explorer 11.
Windows 7
Browser Supported
Internet Explorer 11
Edge No
Safari No
Opera No
Windows 8
Browser Supported
Internet Explorer 11
Safari No
Opera No
Windows 10
Browser Supported
Safari No
Opera No
Apple
Apple always supports the 2 latest releases of the iOS operating system. SAPUI5 supports Apple mobile devices 3 years from the vendor device release date,
except defined otherwise. The following devices are supported:
Android
Android OS based devices are very fragmented in matters of operating system variants and hardware diversity. SAPUI5 supports the following Android
Windows Phone
SAPUI5 supports the following Windows Phone reference device until 3 years from vendor device release date:
1.2.7.8 sap.suite.ui.microchart
Overview of the browser support for sap.suite.ui.microchart library on iOS, Android, Mac OS X, and Windows platforms.
The following tables give an overview of the platforms supported by the sap.suite.ui.microchart library using SAP Blue Crystal theme.
iOS
iOs is supported as of platform version 8.4.1.
Browser Supported
Safari Yes
Web View No
Google Chrome No
Opera No
Android
Android is supported as of platform version 4.4.2.
Browser Supported
Opera No
Opera mini No
Firefox Yes
Mac OS
Mac OS X is supported as of version 10.10.
Browser Supported
Safari Yes
Mozilla Firefox No
Opera No
Windows
Note
Windows Touch is supported as of Windows 8 and Internet Explorer 11.
Windows 7
Browser Supported
Edge No
Safari No
Opera No
Windows 8
Browser Supported
Internet Explorer 11
Safari No
Opera No
Windows 10
Browser Supported
Safari No
Opera No
Apple
Apple always supports the 2 latest releases of the iOS operating system. SAPUI5 supports Apple mobile devices 3 years from the vendor device release date,
except defined otherwise. The following devices are supported:
Android
Android OS based devices are very fragmented in matters of operating system variants and hardware diversity. SAPUI5 supports the following Android
reference devices until 3 years from vendor device release date:
Windows Phone
SAPUI5 supports the following Windows Phone reference device until 3 years from vendor device release date:
1.2.7.9 sap.ushell
PUBLIC Page 54 of 244
2014 SAP SE or an SAP affiliate company. All rights reserved.
1.2.7.9 sap.ushell
Browser support for sap.ushell library on iOS, Android, BlackBerry, MacOS, and Windows platforms
The sap.ushell library supports the same browsers and platforms as sap.m, sap.ui.table, sap.uxap, sap.tnt.
1.2.7.10 sap.makit
Browser support for sap.makit library on iOS, Android, BlackBerry, MacOS, and Windows platforms
iOS
iOs is supported in platform version 5 and 6.
Browser Supported
Safari Yes
Google Chrome No
Opera Mini No
Android
Android is supported as of platform version 2.3 until version 4.2
Browser Supported
Opera No
Opera mini No
Firefox No
BlackBerry
Blackberry is supported as of platform version 10.
Browser Supported
Opera mini No
Mac OS
Safari in version 5.1 or higher is enabled for development scenario, but not productively supported on Mac OS.
Windows
Safari and Chrome are enabled for development scenario, but not productively supported on Windows.
Microsoft Windows
Browser Supported Version
Caution
Touch is not supported for Microsoft Windows.
Mac OS
On Mac OS X platform, Safari browser with version 8 and higher is supported.
Caution
Touch is not supported for Mac OS platforms.
1.2.7.12 sap.gantt
Browser support for the sap.gantt library.
The following sections provide an overview of the platforms supported by the sap.gantt library using the SAP Blue Crystal theme.
Mac OS
On the Mac OS platform, the latest version of Google Chrome is supported.
Caution
Touch is not supported for Mac OS platforms.
Caution
Touch is not supported for Mac OS platforms.
Demo Kit
The Demo Kit contains the following:
Developer Guide with the SAPUI5 documentation.
Explored app, which gives a detailed view of almost every control, including detailed information about the corresponding properties, aggregations,
events and methods. In addition, it also provides samples including a code view that allows you to easily copy any code snippets you may need.
Note
You can also view and download the code for our tutorials here. Just search for Tutorial in the Explored app. For more information, see Tutorials.
API Reference , which includes JavaScript documentation for the framework and control API.
Demo Apps , which also showcase actual samples.
Icons with an overview of all icons that are available with SAPUI5.
You can find the Demo Kit at the following locations:
For SAP NetWeaver Application Server for ABAP, use the following URL, replacing <host> with the host name and <port> with the port number of your
system: http://<host>:<port>/sap/bc/ui5_demokit/
For SAP NetWeaver Application Server for Java, use the following URL, replacing <host> with the host name and <port> with the port number of your
system: http://<host>:<port>/sapui5-sdk
For the SAP HANA Cloud Platform version, go to https://sapui5.hana.ondemand.com/
For the OpenUI5 version, go to https://openui5.hana.ondemand.com/.
Note
You can find the latest released version of the Demo Kit at https://sapui5.hana.ondemand.com/sdk/. If you want to see the Demo Kit for a specific
SAPUI5 version, simple add the version number to the URL, for example https://sapui5.hana.ondemand.com/1.32.7/
Check the available versions with respective maintenance status at https://sapui5.hana.ondemand.com/versionoverview.html.
Platform-Specific Documentation
Note
This is the documentation version for SAP NetWeaver Application Server for ABAP and the User Interface Add-On for SAP NetWeaver.
Note
You can install SAPUI5 on different platforms. The license and maintenance conditions of the respective platforms also apply for SAPUI5. If you use
SAPUI5 tools on SAP HANA Cloud Platform, for example, the license and maintenance conditions of the SAP HANA Cloud Platform apply.
1666368 Installing UI add-ons for SAP NetWeaver Describes the installation of the ABAP components of
UI add-on for SAP NetWeaver
1666369 SPs for UI add-ons for SAP NetWeaver Contains information about add-on support packages
for UI add-on for SAP NetWeaver
1686090 Translation Object for SAPUI5 (ABAP) To use the translation feature with the SAPUI5 ABAP
team provider in the UI add-on in an SAP NetWeaver
7.0 EHP3/7.31 system, implement this SAP Note in the
translation system to enable the translation of text
elements.
1582870 ABAP XSS Escaping Support We highly recommend that you implement this SAP
Note to support ABAP XSS escaping.
Depending on your use case, you can choose one of the following development environments.
Note
You can also build native SAP HANA applications using SAPUI5. For more information, see the SAPUI5 for SAP HANA Development Tutorials in the
SAP HANA Developer Guide on SAP Help Portal at http://help.sap.com/hana_platform/ under Development and Modeling SAP HANA Developer
In this section:
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.
App Development Using SAP Web IDE
SAP Web IDE is a web-based development environment that is optimized for developing SAPUI5 complex apps using the latest innovations, developing
and extending SAP Fiori apps, developing mobile hybrid apps, and extending SAP Web IDE with plugins and templates.
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.
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 roundtrip, and tests execution.
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.
SAPUI5 Runtime Installation (SAP NetWeaver 7.3 EHP1 or Lower)
Installation prerequisites and installation process for SAPUI5 Runtime
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 applications 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.)
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!
}
}
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 .
In this section:
Get a Trial Account and Access SAP Web IDE
Steps for creating an SAP HANA trial account
Start SAP Web IDE
Initial Steps in SAP Web IDE
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 SAPUI5 runtime is located when starting the app.
Create an index.html File
A minimalistic index.html file is needed to test the project configuration. This file contains the SAPUI5 bootstrap and a sap.m.Text control that
displays the text "SAPUI5 is loaded successfully!".
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.
Create a Northwind Destination
Configure a destination in the SAP HANA Cloud Platform Cockpit in order to bypass the same-origin policy of the browser.
After logging on, choose Services in the navigation bar of the SAP HANA Cloud Platform cockpit and open the detailed information on your SAP Web IDE by
choosing the SAP Web IDE tile.
Selecting Open SAP Web IDE leads you to your personal SAP Web IDE.
3. Create your project within the Workspace folder by choosing File New Folder from the menu or Ctrl + Alt + Shift + N . Enter, for example,
myProject as the folder name.
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 SAPUI5,
otherwise the application will be launched with the SAPUI5 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 SAPUI5 is loaded by opening the SAPUI5 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. In SAP-internal
versions of SAP Web IDE, this will load the nightly build of SAPUI5.
Procedure
1. Select the New File icon and enter neo-app.json as the file name.
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 .
Note
For more information on how to run projects, see the documentation of SAP Web IDE at https://help.hana.ondemand.com/webide/frameset.htm?
ec68f058007241be87f5ad4364e6e87b.html.
Field Value
Name northwind
Type HTTP
URL http://services.odata.org
Authentication NoAuthentication
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.
Note
We recommend this development environment only for experienced developers, and only for simple use cases. For all other purposes we recommend
using the SAP Web IDE. For more information, see App Development Using SAP Web IDE.
In this section:
Installing SAPUI5 Tools for Eclipse
Required information for the installation, upgrade, and uninstallation of SAPUI5 tools.
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.
Note
There are differences and specifics for the required software, software components, and installation steps based on the product you are using.
Name Technical ID
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 backend 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: Mars 4.5.0 and Neon 4.6.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 Technical IDE Min. version Contained in Eclipse JEE Contained in Eclipse Classic
Bundle Bundle
Note
Make sure that you have write permission for the directory you use for the Eclipse installation, or start Eclipse as Administrator.
SAP NetWeaver Download the following components from the SAP Software Download Center on
SAP Service Marketplace at http://service.sap.com/swdc:
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, download the SAPUI5
TOOLS IDE PLUGIN 1.00 component from the SAP Software Download Center on
SAP Service Marketplace at http://service.sap.com/swdc.
SAP HANA Cloud Platform For the installation of SAPUI5 on SAP HANA Cloud Platform, the SAPUI5 tools are
contained in the SAP HANA Cloud Tools and you can install them as described in
https://tools.hana.ondemand.com/#sapui5.
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.
In this section:
Create an SAPUI5 Application Project
To create an SAPUI5 Application Project, you must have installed the SAPUI5 Application Development feature in your Eclipse installation.
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.
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.
Create an Additional View
A SAPUI5 application view can only be created for a SAPUI5 application project that has been created with the SAPUI5 Application Wizard and not for
other kinds of projects.
Integrate a New View
To integrate a new view, you can either add it to index.html or nest it into another view.
JavaScript Code Completion
When using the SAPUI5 tools, code completion is enabled automatically, without the tools, you need to enable it.
Linking your Eclipse Editor to the Demo Kit
You can use Quick Fixes to display the API documentation of a SAPUI5 control in the Demo Kit.
Procedure
1. Start the New SAPUI5 Application Project wizard in the Eclipse by choosing New Other ... SAPUI5 Application Development Application
Project .
Next Steps
Add a Control to Your View
Implement a Method in the Controller
Create an Additional View
Integrate a New View
Related Information
JavaScript Code Completion
Use JavaScript and XML Templates
SAPUI5 Snippets
Linking your Eclipse Editor to the Demo Kit
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));
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
Prerequisites
You've created a button as described in: Add a Control to Your View
Procedure
To handle this event, add the following function to the controller:
doIt : function(oEvent) { alert(oEvent.getSource().getId() + " does it!"); }
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 Other... SAPUI5 Application Development View to open the New SAPUI5 Application View wizard.
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.
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
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:
<core:View controllerName="sap.hcm.Address" xmlns="sap.ui.commons" xmlns:core="sap.ui.core"
xmlns:html="http://www.w3.org/1999/xhtml">
<Panel>
<core:JSView id="myJSView" viewName="sap.hcm.Bankaccount" />
</Panel>
<core:View>
For HTML views, the nested view looks as follows:
</template>
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.
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.
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 1.2.9.3.2.9 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:
1. Choose Window Show View Other... .
2. In the Show View dialog, choose General Snippets and confirm you selection with OK .
The Snippet view opens.
Results
The page should then be displayed correctly:
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
Open in External Browser
Note
Before deploying a 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>
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;
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.
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 General Network Connections . Here you can specify the proxy entries and the proxy bypass.
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.
Building SAPUI5
Grunt is used to build a production version of SAPUI5. The build result is located inside the directory target/openui5.
Optionally, you can choose to build selected libraries only or skip copying the test-resources folder.
Caution
As the build infrastructure is still being migrated from the old Maven-based one, the Grunt build does not yet have all desired capabilities. This means that
the build result is not currently completely optimized for size and performance. If you intend to use SAPUI5 for productive purposes, we recommend using
the runtime binaries provided at http://openui5.org .
In this section:
Installing the 1.2.9.4 Node.js-Based Development Environment
Installation steps for Node.js
Testing SAPUI5
Test your development using ESLint or unit tests.
Common Installation Issues
Common installation issues that you might face when using the node.js based development environment
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 HTTP_PROXY=http://proxy:8080
@SET HTTPS_PROXY=http://proxy:8080
@SET FTP_PROXY=http://proxy:8080
@SET 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 SAPUI5 is running: http://localhost:8080/testsuite/.
Testing SAPUI5
Test your development using ESLint or unit tests.
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
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.
"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.
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 Details
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 .
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.
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 instead, you can 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.
Example:
<script>
<!-- put the following code in the beginning of the application code -->
function appReady(){
sap.ui.getCore().setModel(new sap.ui.model.odata.ODataModel(<ODATA_URL>, false));
}
<!-- bind to the deviceready event -->
document.addEventListener("deviceready", appReady, false);
</script>
1.3 Tutorials
The SAPUI5 tutorials introduce you to all major development paradigms of SAPUI5 using practical examples in an interactive format.
Each tutorial has a different focus and guides you through the major topics of SAPUI5, helping you learn best practices for building apps with SAPUI5. The
detailed documentation described here can be used in combination with downloadable code in the Explored app in the Demo Kit.
You can use your preferred development environment to go through the steps of each tutorial. Each step explains a specific feature or aspect of SAPUI5 by
way of small incremental changes. The code that is added in each step is highlighted in the code blocks and followed by a detailed explanation.
Note
The apps that we build throughout these tutorials might simplify some aspects of real app development in order to focus on the plot line. (We admit that in
some places, the UI might seem silly or unrealistic.) Please refer to the rest of the SAPUI5 documentation to dive deeper into the various topics,
especially those described under Developing Apps and Essentials.
Learning Path
If you only want a first insight featuring the very basics, try the short Hello World tutorial.
If you are new to SAPUI5 or would like to get a refresher on the latest best practices, start with the Walkthrough .
After that, you can proceed with the other tutorials that build up on that knowledge and go into more detail on specific topics like data binding, navigation and
routing, using smart controls.
Prerequisites
You should be familiar with JavaScript.
Set up your development environment.
You can use any development environment or IDE of your choice that is suitable for Web development. If you dont know which development
environment to use, we recommend trying the SAP Web IDE. It is a cloud-based development environment optimized for SAPUI5 app development and
includes a cloud runtime to test the app.
For more information on how to set up your development environment, see Development Environment.
Set up a (local) Web server to run the app that is being built throughout this tutorial. (In SAP Web IDE, this is already included).
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.
Troubleshooting
If you get stuck on a certain tutorial step, please have a look at the developer console of your browser. Are there log messages displayed related to the error?
Did it work before, and your recent change broke the app? Try reverting the previous change and test if the app can be run again. The most common errors are
as follows:
Error message sap is not defined in the console and the page stays blank
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 SAPUI5 resources are located (typically globally under /resources or locally under resources).
Note
If you are running the tutorial in SAP Web IDE, you will have to configure the project descriptor neo-app.json. In this descriptor file, the path to
the resources is already configured. Other development environments might need the resources to be copied to the server. Alternatively, you can
use the CDN version of https://sapui5.hana.ondemand.com/resources/sap-ui-core.js.
If these hints still don't help fix the problem in your app project, try downloading the solution of the current step or the next step of the tutorial from the
Explored app. This should get your project fixed again, just dont forget to check the resource path and the project configuration files again.
Please also check the generic troubleshooting section here: Troubleshooting.
Preview
Note
You can view the demo app in the Demo Kit under 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". For accessing SAPUI5 on the SAP HANA Cloud Platform, for
example, use src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js".
You can use this reference to the latest stable version of SAPUI5 for the tutorial or for testing purposes, but never use this for productive use. In an actual
app, you always have to specify an SAPUI5 version explicitly.
For more information, see Step 1: Create an HTML Page and Variant for Bootstrapping from Content Delivery Network.
<!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_bluecrystal"
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", {
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_bluecrystal"
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 SAPUI5 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". For accessing SAPUI5 on the SAP HANA
Cloud Platform, for example, use src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js".
You define which library is loaded, and which theme should be used. In our example, only the sap.m library and the sap_bluecrystal theme
are loaded. You can load additional libraries and themes if you like.
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, SAPUI5 including controls is loaded and ready to use.
Related Information
Bootstrapping: Loading and Initializing
Initialization Process
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_bluecrystal"
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 SAPUI5 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.
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.
index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
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.
Next: Summary
1.3.4.18 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.
1.3.2 Walkthrough
PUBLIC Page 85 of 244
2014 SAP SE or an SAP affiliate company. All rights reserved.
1.3.2 Walkthrough
In this tutorial we will introduce you to all major development paradigms of SAPUI5.
Despite the simplicity of the UI, we strive for a proper setup of modules, structuring of application code and adherence to code conventions. As such this
tutorial represents a best practice for building apps with SAPUI5.
The UI might be a bit inconsistent in some places dont take it as a UI design guideline. We'll show you around using minimal code changes in each step.
Caution
Some steps of this tutorial use features that are only available as of SAPUI5 version 1.30. If you use a lower version of the library you might not be able to
see the expected result. Whenever suitable, we point out alternatives for lower releases.
We first introduce you to the basic development paradigms like Model-View-Controller and establish the basic 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 SAPUI5 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 SAPUI5.
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 Explored app in the demo kit under 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 Tutorials):
Prerequisites
Outline of the Steps of Each Tutorial
Downloading Code for a Tutorial Step
Adapting Code to Your Development Environment
Troubleshooting
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 1 .
webapp/index.html (New)
<!DOCTYPE html >
<html>
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.
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 2 .
webapp/index.html
Note
SAPUI5 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 SAPUI5 is deployed locally in the resources folder of your Web server's root context.
If SAPUI5 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. SAPUI5 can also be retrieved from the
Content Delivery Network (CDN) at https://sapui5.hana.ondemand.com/resources/sap-ui-core.js .
You can use this reference to the latest stable version of SAPUI5 for the tutorial or for testing purposes, but never use this for productive use. In an actual
app, you always have to specify an SAPUI5 version explicitly.
For more information about the CDN, see Variant for Bootstrapping from Content Delivery Network.
In case you are using SAP Web IDE, you can right-click the project and select New Cloud Connectivity Configuration 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>
Related Information
Bootstrapping: Loading and Initializing
Preload Variant for Bootstrapping
Compatibility Version Information
Variant for Bootstrapping from Content Delivery Network
https://jquery.org/
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 3 .
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. 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
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 4 .
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 is located. We define an additional sap.ui.core.mvc namespace with alias
mvc, where the SAPUI5 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 SAPUI5, each control has its own ID. In the XML view above we did not specify an ID attribute, and therefore the SAPUI5 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_bluecrystal"
data-sap-ui-libs="sap.m"
data-sap-ui-compatVersion="edge"
data-sap-ui-preload="async"
data-sap-ui-resourceroots='{
"sap.ui.demo.wt": "./"
}' >
</script>
<script>
sap.ui.getCore().attachInit(function () {
sap.ui.xmlview({
viewName : "sap.ui.demo.wt.view.App"
}).placeAt("content");
});
</script>
</head>
<body class="sapUiBody" id="content">
</body>
</html>
We tell SAPUI5 core that resources in the sap.ui.demo.wt 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 SAPUI5 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.wt.view in order to
uniquely identify this resource.
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)
Views
XML View
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 5 .
webapp/view/App.view.xml
<mvc:View
controllerName="sap.ui.demo.wt.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.
webapp/controller/App.controller.js (New)
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function (Controller) {
"use strict";
return Controller.extend("", {
});
});
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 its 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.wt.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 SAPUI5 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
Related Information
Model View Controller (MVC)
Controller
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 6 .
webapp/controller/App.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast"
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).
Preview
Figure 1: An input field and a description displaying the value of the input field
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 7 .
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.wt.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 SAPUI5s 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.
webapp/view/App.view.xml
<mvc:View
controllerName="sap.ui.demo.wt.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 SAPUI5 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".
/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_bluecrystal"
data-sap-ui-libs="sap.m"
data-sap-ui-compatVersion="edge"
data-sap-ui-preload="async"
data-sap-ui-resourceroots='{
"sap.ui.demo.wt": "./"
}' >
</script>
<script>
sap.ui.getCore().attachInit(function () {
sap.ui.xmlview({
viewName: "sap.ui.demo.wt.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.
Conventions
Use Hungarian notation for variable names.
Related Information
Preview
Figure 1: 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 in the Explored app in the Demo Kit under 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.wt.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, SAPUI5 will load the
language file that fits best to the user's environment.
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.wt.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.wt.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.wt.i18n.i18n consists of the application namespace sap.ui.demo.wt (the application root as defined in the
index.html), the folder name i18n and finally the file name i18n without extension. The SAPUI5 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
webapp/view/App.view.xml
<mvc:View
controllerName="sap.ui.demo.wt.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 sayHelloButtonText 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
Preview
Figure 1: 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 in the Explored app in the Demo Kit under Walkthrough - Step 9 .
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
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 SAPUI5 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.wt.Component", {
metadata : {
rootView: "sap.ui.demo.wt.view.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);
webapp/controller/App.controller.js
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_bluecrystal"
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.wt": "./"
}' >
</script>
<script>
sap.ui.getCore().attachInit(function () {
new sap.ui.core.ComponentContainer({
name : "sap.ui.demo.wt"
}).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 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
Preview
Figure 1: 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 in the Explored app in the Demo Kit under Walkthrough - Step 10 .
Caution
Automatic model instantiation is only available as of SAPUI5 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.
webapp/manifest.json (New)
{
"_version": "1.1.0",
"sap.app": {
"_version": "1.1.0",
"id": "sap.ui.demo.wt",
"type": "application",
"i18n": "i18n/i18n.properties",
"title": "{{appTitle}}",
"description": "{{appDescription}}",
"applicationVersion": {
"version": "1.0.0"
}
},
"sap.ui": {
"_version": "1.1.0",
"technology": "UI5",
"deviceTypes": {
"desktop": true,
"tablet": true,
"phone": true
},
"supportedThemes": [
"sap_bluecrystal"
]
},
"sap.ui5": {
"_version": "1.1.0",
"rootView": "sap.ui.demo.wt.view.App",
"dependencies": {
"minUI5Version": "1.30",
"libs": {
"sap.m": {}
}
},
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"settings": {
"bundleName": "sap.ui.demo.wt.i18n.i18n"
}
}
}
}
}
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 SAPUI5 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
Note
Properties of the resource bundle are enclosed in two curly brackets in the descriptor. This is not a SAPUI5 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.
webapp/i18n/i18n.properties
# App Descriptor
appTitle=Hello World
appDescription=A simple walkthrough app that explains the most important concepts of SAPUI5
# 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.wt.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 SAPUI5 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 SAPUI5, 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 SAPUI5 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 SAPUI5 version still use the Component.js file for this
purpose - so it is still supported, but not recommended.
Related Information
Descriptor for Applications, Components, and Libraries
Preview
Figure 1: A panel is now displaying the controls from the previous steps
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 11 .
webapp/view/App.view.xml
<mvc:View
controllerName="sap.ui.demo.wt.controller.App"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
displayBlock="true">
<App>
<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.
webapp/i18n/i18n.properties
# App Descriptor
appTitle=Hello World
appDescription=A simple walkthrough app that explains the most important concepts of SAPUI5
# 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.
Preview
Figure 1: The app is now run in a shell that limits the app width
Coding
You can view and download all files in the Explored app in the Demo Kit under 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.wt",
height : "100%"
})
}).placeAt("content");
});
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.
Preview
Figure 1: The layout of the panel and its content now has margins and padding
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 13 .
webapp/view/App.view.xml
<mvc:View
controllerName="sap.ui.demo.wt.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"/>
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.
Conventions
Use the standard SAPUI5 CSS classes for the layout if possible.
Related Information
Using Predefined CSS Margin Classes
Using Container Content Padding CSS Classes
Preview
Figure 1: The space between the button and the input field is now smaller and the output text is blue
Caution
As stated in the Compatibility Rules, the HTML and CSS generated by SAPUI5 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 SAPUI5 is updated. A prerequisite for this is that
you have control over the version of SAPUI5 being used, for example in a standalone scenario. This is not possible when running your app in the SAP Fiori
launchpad where SAPUI5 is centrally loaded for all apps. As such, SAP Fiori launchpad apps should not override styles.
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 14 .
webapp/css/style.css (New)
.myAppDemoWT .myCustomButton.sapMBtn {
margin-right: 0.125rem
}
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. SAPUI5 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.
webapp/view/App.view.xml
<mvc:View
controllerName="sap.ui.demo.wt.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%"/>
<Text
text="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
Conventions
Do not specify colors in custom CSS but use the standard theme-dependent classes instead.
Related Information
Descriptor for Applications, Components, and Libraries
CSS Classes for Theme Parameters
Creating Themable User Interfaces
Compatibility Rules
Preview
Figure 1: The panel content is now refactored to a separate view (No visual changes to last step)
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 15 .
webapp/view/App.view.xml
<mvc:View
controllerName="sap.ui.demo.wt.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.wt.view.HelloPanel"/>
</content>
</Page>
</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
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.wt.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);
}
});
});
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.wt.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.
Preview
Figure 1: A dialog opens when the new Say Hello With Dialog button is clicked
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 16 .
webapp/view/HelloPanel.view.xml
<mvc:View
controllerName="sap.ui.demo.wt.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="sapUiDemoWTmyCustomButton"/>
<Input
value="{/recipient/name}"
valueLiveUpdate="true"
width="60%"/>
<Text
text="Hello {/recipient/name}"
class="sapUiSmallMargin sapThemeHighlight-asColor sapUiDemoWTmyCustomText"/>
</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 panels content view.
webapp/view/HelloDialog.fragment.xml (New)
<core:FragmentDefinition
xmlns="sap.m"
xmlns:core="sap.ui.core" >
<Dialog
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.
webapp/i18n/i18n.properties
# App Descriptor
appTitle=Hello World
appDescription=A simple walkthrough app that explains the most important concepts of SAPUI5
# 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 dialogs 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
Dialogs and other Popups as Fragments
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under 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.wt.controller.HelloPanel", {
onShowHello : function () {
},
_getDialog : function () {
// create dialog lazily
if (!this._oDialog) {
// create dialog via fragment factory
this._oDialog = sap.ui.xmlfragment("sap.ui.demo.wt.view.HelloDialog", this);
// connect dialog to view (models, lifecycle)
this.getView().addDependent(this._oDialog);
}
return this._oDialog;
},
onOpenDialog : function () {
this._getDialog().open();
},
onCloseDialog : function () {
this._getDialog().close();
}
});
});
As previously described, fragments are pure UI reuse artifacts and do not have a controller. The second 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 second 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
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
Related Information
Reusing UI Parts: Fragments
Instantiation of Fragments
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 18 .
webapp/view/HelloPanel.view.xml
<mvc:View
controllerName="sap.ui.demo.wt.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="sapUiDemoWTmyCustomButton"/>
<Input
value="{/recipient/name}"
valueLiveUpdate="true"
width="60%"/>
<Text
text="Hello {/recipient/name}"
class="sapUiSmallMargin sapThemeHighlight-asColor sapUiDemoWTmyCustomText"/>
Tip
You can look up other icons using the Icon Explorer in the Demo Kit.
The 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
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.
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 19 .
webapp/Component.js
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/model/json/JSONModel",
"sap/ui/demo/wt/controller/HelloDialog"
], function (UIComponent, JSONModel, HelloDialog) {
"use strict";
return UIComponent.extend("sap.ui.demo.wt.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();
}
});
});
The dialog instantiation is refactored to a new helper object that we can directly access through the component. In its initialization method we store a public
reference to it that can be accessed from every controller.
webapp/controller/HelloDialog.js (New)
sap.ui.define([
"sap/ui/base/Object"
], function (Object) {
"use strict";
return Object.extend("sap.ui.demo.wt.controller.HelloDialog", {
_getDialog : function () {
// create dialog lazily
if (!this._oDialog) {
// create dialog via fragment factory
this._oDialog = sap.ui.xmlfragment("sap.ui.demo.wt.view.HelloDialog", this);
}
return this._oDialog;
},
open : function (oView) {
var oDialog = this._getDialog();
// connect dialog to view (models, lifecycle)
oView.addDependent(oDialog);
Our _getDialog method is refactored from the HelloPanel controller and instantiates our dialog fragment as in the previous steps. Note that now the
reuse object is passed on as a controller to the fragment.
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.
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.wt.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().helloDialog.open(this.getView());
}
});
});
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.wt.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.wt.view.HelloPanel"/>
</content>
</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.wt.controller.App", {
onOpenDialog : function () {
this.getOwnerComponent().helloDialog.open(this.getView());
});
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.
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under 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,
"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"
},
webapp/manifest.json
{
"sap.ui5": {
"_version": "1.1.0",
"rootView": "sap.ui.demo.wt.view.App",
"dependencies": {
"minUI5Version": "1.30",
"libs": {
"sap.m": {}
}
},
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"settings": {
"bundleName": "sap.ui.demo.wt.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 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 SAPUI5 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.
webapp/view/App.view.xml
<mvc:View
controllerName="sap.ui.demo.wt.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>
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>
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 SAPUI5
# 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
Aggregation Binding
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 21 .
webapp/view/InvoiceList.view.xml
<mvc:View
controllerName="sap.ui.demo.wt.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>
</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)
PUBLIC Page 120 of 244
2014 SAP SE or an SAP affiliate company. All rights reserved.
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.wt.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.
Related Information
Calculated Fields for Data Binding
Custom Formatter Functions
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 22 .
webapp/view/InvoiceList.view.xml
<mvc:View
controllerName="sap.ui.demo.wt.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
PUBLIC Page 122 of 244
2014 SAP SE or an SAP affiliate company. All rights reserved.
Conventions
Only use expression binding for trivial calculations.
Related Information
Expression Binding
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 23 .
webapp/model/formatter.js (New)
sap.ui.define([], function () {
"use strict";
return {
statusText: function (sStatus) {
var resourceBundle = this.getView().getModel("i18n").getResourceBundle();
switch (sStatus) {
case "A":
return resourceBundle.getText("invoiceStatusA");
case "B":
return resourceBundle.getText("invoiceStatusB");
As the JSDoc comment block in front of the function says, the function statusText gets the technical status from the data model as input parameter and
returns a human-readable 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/wt/model/formatter"
], function (Controller, JSONModel, formatter) {
"use strict";
return Controller.extend("sap.ui.demo.wt.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.wt.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' }">
<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
# 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.
Next Step
Continue with Step 24: Filtering.
Related Information
Using Complex Syntax to Define a Formatter
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 24 .
webapp/view/InvoiceList.view.xml
<mvc:View
controllerName="sap.ui.demo.wt.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" selectOnFocus="false"/>
</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. Since we want the cursor to stay at the same
position, we set the flag selectOnFocus to false.
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([
// filter binding
var oList = this.getView().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.
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 25 .
webapp/view/InvoiceList.view.xml
<mvc:View
controllerName="sap.ui.demo.wt.controller.InvoiceList"
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.
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 26 .
webapp/manifest.json
{
"_version": "1.1.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": {
"_version": "1.1.0",
"rootView": "sap.ui.demo.wt.view.App",
"dependencies": {
"minUI5Version": "1.30",
"libs": {
"sap.m": {}
}
},
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.
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 browsers console:
Note
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
apps domain/subdomain, protocol, or port.
The browser refuses to connect to a remote URL directly for security reasons and we need a workaround.
For productive apps this approach is not recommended we will describe the options later in this step.
Tip
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. 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. Be aware that its 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
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.
Note
In the component, we have added a dependency to sap.ui.model.odata.v2.ODataModel. The v2 in the namespace stands for the second version
of the ODataModel implementation in SAPUI5. This v2 is not related to the "OData Version 2" specification. We recommend suggested to use the new
implementation sap.ui.model.odata.v2.ODataModel instead of the old sap.ui.model.odata.ODataModel in your SAPUI5 apps.
/destinations/northwind/V2/Northwind/Northwind.svc/$metadata http://services.odata.org/V2/Northwind/Northwind.svc/$metadata
/destinations/northwind/V2/Northwind/Northwind.svc/Invoices http://services.odata.org/V2/Northwind/Northwind.svc/Invoices
The destination itself is configured inside the SAP HANA Cloud Platform Cockpit. To set up the destination for the Northwind OData service follow the
configuration steps described under Create a Northwind Destination.
In the following, we describe how to use the destination in our app to connect to the Northwind OData service.
Note
This section is describing the setup for the SAP Web IDE only. If you are using a different development environment, you can either create a simple proxy
service by yourself or use an existing one.
If it does not exist yet, create the file and reference the Northwind destination there. Just copy the content of the code block above into that file and try to run
the app again.
Note
If the file already exists, for example, because you already created it to map the SAPUI5 resources as described in Step 2 of this tutorial, just append the
destination to the existing route definitions.
Now you can run the app even without disabling the same-origin policy in your browser. In other words, the app can be used by any user even without changing
the browser settings.
webapp/manifest.json
{
"_version": "1.1.0",
"sap.app": {
...
"dataSources": {
"invoiceRemote": {
"uri": "/destinations/northwind/V2/Northwind/Northwind.svc/",
"type": "OData",
"settings": {
"odataVersion": "2.0"
}
}
}
},
"sap.ui": {
Related Information
Use a SimpleProxyServlet for Testing to Avoid Cross-domain Requests
OData Home Page
Preview
Coding
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.xmlservice 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>Walkthrough</title>
<script
id="sap-ui-bootstrap"
src="/resources/sap-ui-core.js"
data-sap-ui-theme="sap_bluecrystal"
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.wt": "./"
}' >
</script>
<script>
sap.ui.getCore().attachInit(function () {
new sap.m.Shell({
app : new sap.ui.core.ComponentContainer({
name : "sap.ui.demo.wt",
height : "100%"
})
}).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
new Shell({
app : new ComponentContainer({
height: "100%",
name: "sap.ui.demo.wt"
})
}).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.
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",
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>
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
});
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 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
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 28 .
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.
webapp/test/unit/model/formatter.js
sap.ui.require(
[
"sap/ui/demo/wt/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", {
setup: function () {
this._oResourceModel = new ResourceModel({
bundleUrl : jQuery.sap.getModulePath("sap.ui.demo.wt", "/i18n/i18n.properties")
});
},
teardown: function () {
this._oResourceModel.destroy();
}
});
QUnit.test("Should return the translated texts", function (assert) {
// Arrange
var oViewStub = {
getModel: this.stub().withArgs("i18n").returns(this._oResourceModel)
};
var oControllerStub = {
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
stubs 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.wt": "../../",
"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 wont further explain these parts but focus on the application parts instead.
Lets 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/service/unit/unitTests.qunit.html file in
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
QUnit Testing Fundamentals
QUnit Home Page
Sinon.JS Home Page
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 29 .
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)
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/src/test", ".html"));
//Actions
When.onTheAppPage.iPressTheSayHelloWithDialogButton();
// Assertions
Then.onTheAppPage.iShouldSeeTheHelloDialog().
and.iTeardownMyAppFrame();
});
});
Lets 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");
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 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.wt.test.integration": "./",
"sap.ui.demo.src.test" : "../../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.wt.test.integration.pages.App");
// journeys
jQuery.sap.require("sap.ui.demo.wt.test.integration.navigationJourney");
</script>
</head>
<body>
<div id="content"></div>
<div id="qunit"></div>
<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 SAPUI5 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
Related Information
Integration Testing with One Page Acceptance Tests (OPA5)
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 30 .
webapp/view/InvoiceList.view.xml
<mvc:View
controllerName="sap.ui.demo.wt.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/>
Note
If you use the Google Chrome browser, you can install the UI5 Inspector plugin. With this plugin, you can easily debug your SAPUI5- or -based apps. For
more information, see UI5 Inspector.
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 SAPUI5 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
SAPUI5 files are shipped in a minified version, this means that all possible variable names are shortened and comments are removed.
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 browsers 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 SAPUI5 artifacts.
Conventions
As per SAPUI5 convention uncompressed source files end with *-dbg.js
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 31 .
webapp/manifest.json
{
"_version": "1.1.0",
"sap.ui5": {
"models": {
},
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "sap.ui.demo.wt.view",
"controlId": "app",
"controlAggregation": "pages"
},
"routes": [
{
"pattern": "",
"name": "overview",
"target": "overview"
},
{
"pattern": "detail",
"name": "detail",
"target": "detail"
}
],
"targets": {
"overview": {
"viewName": "Overview"
},
"detail": {
"viewName": "Detail"
}
}
}
}
webapp/Component.js
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/model/json/JSONModel",
"sap/ui/demo/wt/controller/HelloDialog",
], function (UIComponent, JSONModel, HelloDialog) {
"use strict";
return UIComponent.extend("sap.ui.demo.wt.Component", {
metadata: {
manifest: "json"
},
init: function () {
// set dialog
this.helloDialog = new HelloDialog();
// create the views based on the url/hash
this.getRouter().initialize();
}
});
});
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.wt.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.wt.view.HelloPanel"/>
<mvc:XMLView viewName="sap.ui.demo.wt.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.wt.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.wt.controller.App"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
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.wt.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'}],
type: 'sap.ui.model.type.Currency',
formatOptions: {
showMeasure: false
}
}"
numberUnit="{view>/currency}"
numberState="{= ${invoice>ExtendedPrice} > 50 ? 'Error' : 'Success' }"
type="Navigation"
press="onPress">
<firstStatus>
<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/wt/model/formatter",
"sap/ui/model/Filter",
"sap/ui/model/FilterOperator"
return Controller.extend("sap.ui.demo.wt.controller.InvoiceList", {
});
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
Tutorial: Navigation and Routing
Preview
Figure 1: The selected invoice details are now shown in the details page
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 32 .
webapp/manifest.json
{
"_version": "1.1.0",
"sap.ui5": {
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "sap.ui.demo.wt.view",
"controlId": "app",
webapp/view/Detail.view.xml
<mvc:View
controllerName="sap.ui.demo.wt.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 items 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/wt/model/formatter",
"sap/ui/model/Filter",
"sap/ui/model/FilterOperator"
], function (Controller, JSONModel, formatter, Filter, FilterOperator) {
"use strict";
return Controller.extend("sap.ui.demo.wt.controller.InvoiceList", {
webapp/controller/Detail.controller.js (New)
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function (Controller) {
"use strict";
return Controller.extend("sap.ui.demo.wt.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 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 SAPUI5 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
Tutorial: Navigation and Routing
Preview
Coding
webapp/view/Detail.view.xml
<mvc:View
controllerName="sap.ui.demo.wt.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>Quantity} x {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.wt.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();
Conventions
Add a path to go back to the parent page when the history state is unclear.
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 34 .
webapp/control/ProductRating.js (New)
sap.ui.define([
"sap/ui/core/Control"
], function (Control) {
"use strict";
return Control.extend("sap.ui.demo.wt.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 SAPUI5 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 SAPUI5 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 instantiates in a view. It is usually called
initially by the core of SAPUI5 and whenever a property of the control is changed. The parameter oRM of the render function is the SAPUI5 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 SAPUI5 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 SAPUI5 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"
this.setValue(fValue);
this.getAggregation("_rating").setEnabled(false);
this.getAggregation("_label").setText(oResourceBundle.getText("productRatingLabelFinal"));
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
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.
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 SAPUI5 controls, which are in fact build similar to custom controls.
In the init function that is called by SAPUI5 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.
Lets ignore the other internal helper functions and event handlers for now and define our renderer. With the help of the SAPUI5 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. SAPUI5 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 SAPUI5 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.
And last but not least, 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" 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.
webapp/view/Detail.view.xml
<mvc:View
controllerName="sap.ui.demo.wt.controller.Detail"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
xmlns:wt="sap.ui.demo.wt.control">
<Page
title="{i18n>detailPageTitle}"
showNavButton="true"
navButtonPress="onNavBack">
<ObjectHeader
intro="{invoice>ShipperName}"
title="{invoice>ProductName}"/>
<wt:ProductRating 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.wt.controller.Detail, {
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 SAPUI5.
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!
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.
Related Information
Developing SAPUI5 Controls
Defining the Control Metadata
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 35 .
webapp/view/InvoiceList.view.xml
<mvc:View
controllerName="sap.ui.demo.wt.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}"/>
</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 tablet and 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 ColumnListem 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
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 tablet
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 SAPUI5
# Hello Panel
showHelloButtonText=Say Hello
helloMsg=Hello {0}
# 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.
Related Information
Configuring Responsive Behavior of a Table
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 36 .
webapp/view/HelloPanel.view.xml
<mvc:View
controllerName="sap.ui.demo.wt.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="myAppDemoWTmyCustomButton"/>
<Input
value="{/recipient/name}"
valueLiveUpdate="true"
width="60%"/>
<Text
text="Hello {/recipient/name}"
class="sapUiSmallMargin sapThemeHighlight-asColor myAppDemoWTmyCustomText"/>
</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 SAPUI5 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 SAPUI5 offers more functionality to detect various device-specific settings, please have a look at the documentation for more details.
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([
"sap/ui/core/UIComponent",
"sap/ui/model/json/JSONModel",
"sap/ui/demo/wt/controller/HelloDialog",
"sap/ui/Device"
], function (UIComponent, JSONModel, HelloDialog, Device) {
"use strict";
return UIComponent.extend("sap.ui.demo.wt.Component", {
metadata: {
manifest: "json"
},
init: function () {
// call the init function of the parent
UIComponent.prototype.init.apply(this, arguments);
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 SAPUI5 are bidirectional (TwoWay). When the property changes, the bound model value is updated as
well.
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 SAPUI5 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.wt.controller.Detail"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
xmlns:wt="sap.ui.demo.wt.control">
<Page
title="{i18n>detailPageTitle}"
showNavButton="true"
navButtonPress="onNavBack">
<ObjectHeader
responsive="true"
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.wt.controller.Detail, {
onInit : function () {
var oViewModel = new JSONModel({
currency: "EUR"
});
this.getView().setModel(oViewModel, "view");
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.
Preview
Figure 1: The content density is compact on desktop devices and cozy on touch-enabled devices
Coding
You can view and download all files in the Explored app in the Demo Kit under Walkthrough - Step 37 .
webapp/Component.js
...
init: function ()
},
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. SAPUI5 controls can be displayed in multiple sizes,
for example in a compact size that is optimized for desktop and non-touch 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.wt.controller.App", {
onInit: function () {
this.getView().addStyleClass(this.getOwnerComponent().getContentDensityClass());
webapp/controller/HelloDialog.js
sap.ui.define([
"sap/ui/base/Object"
], function (Object) {
"use strict";
return Object.extend("sap.ui.demo.wt.controller.HelloDialog", {
_getDialog: function () {
// create dialog lazily
if (!this._oDialog) {
// create dialog via fragment factory
this._oDialog = sap.ui.xmlfragment("sap.ui.demo.wt.view.HelloDialog", this);
}
return this._oDialog;
},
open: function (oView) {
var oDialog = this._getDialog();
// forward compact/cozy style into Dialog
jQuery.sap.syncStyleClass(oView.getController().getOwnerComponent().getContentDensityClass(), oView, oDialog);
// open dialog
oDialog.open();
},
onCloseDialog: function () {
this._getDialog().close();
}
});
});
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.
This was the last step, you have successfully completed the Walkthrough!
Next: Summary
Related Information
Content Densities
1.3.4.18 Summary
You should now be familiar with the major development paradigms and concepts of SAPUI5 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.
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 Explored app in the demo kit under 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 Tutorials):
Prerequisites
Outline of the Steps of Each Tutorial
Downloading Code for a Tutorial Step
Adapting Code to Your Development Environment
Troubleshooting
Related Information
Data Binding
Model View Controller (MVC)
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under 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"
src="../resources/sap-ui-core.js"
data-sap-ui-theme="sap_bluecrystal"
data-sap-ui-libs="sap.m"
data-sap-ui-compatVersion="edge"
data-sap-ui-preload="async"
>
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.
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.
Preview
Figure 1: Screen with text derived from a model object (No visual changes to last step)
Coding
You can view and download all files in the Explored app in the Demo Kit under Data Binding - Step 1 .
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);
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 SAPUI5 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
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
JSON Model
Preview
Figure 1: Screen with text derived from various sources (No visual changes to last step)
Coding
You can view and download all files in the Explored app in the Demo Kit under 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"
});
Related Information
Binding Controls to the Model
Property Binding
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Data Binding - Step 4 .
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_bluecrystal"
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);
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 SAPUI5 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.
SAPUI5 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
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Data Binding - Step 5 .
webapp/index.html
...
<script>
var oModel = new sap.ui.model.json.JSONModel({
firstName : "Harry",
lastName : "Hawk",
enabled : true,
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 the API Reference for
sap.ui.base.ManagedObject.bindProperty in the Demo Kit.
Note
There are two important points to understand about alterations to a model object's data binding mode:
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.
Preview
Figure 1: Texts derived from the resource model (No visual change to last step)
Coding
You can view and download all files in the Explored app in the Demo Kit under 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);
// Assign the model object to the SAPUI5 core using the name "i18n"
sap.ui.getCore().setModel(oResourceModel, "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>
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
Preview
webapp/i18n/i18n_de.properties (New)
# Field labels
firstName=Vorname
lastName=Nachname
enabled=Aktiviert
# Screen titles
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
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Data Binding - Step 8 .
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"
}
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 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.
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Data Binding - Step 9 .
webapp/controller/App.controller.js (New)
sap.ui.define(["sap/ui/core/mvc/Controller"],
function (Controller) {
"use strict";
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"));
}
});
});
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. 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">
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 tellsSAPUI5 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 cant 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
Related Information
Custom Formatter Functions
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Data Binding - Step 10 .
webapp/index.html
<script>
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"
});
...
We create two new model properties salesToDate and currencyCode.
webapp/view/App.view.xml
...
<Panel headerText="{i18n>panel2HeaderText}" class="sapUiResponsiveMargin" width="auto">
<content>
<l:HorizontalLayout>
<l:VerticalLayout>
<Label class="sapUiSmallMargin" text="{i18n>address}:" />
<Text class="sapUiSmallMarginBegin sapUiSmallMarginBottom" text="{/address/street}\n{/address/zip} {/address/city}\n{/address
<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: {sh
</l:VerticalLayout>
</l:HorizontalLayout>
</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_de.properties
# Field labels
firstName=Vorname
lastName=Nachname
enabled=Aktiviert
address=Adresse
salesToDate=Verkufe bis zum heutigen Datum
...
Add the missing texts to the properties files.
Related Information
Using the Data Binding Type System
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Data Binding - Step 11 .
webapp/index.html
...
<script>
...
// Assign the model object to the SAPUI5 core
sap.ui.getCore().setModel(oModel);
sap.ui.getCore().setModel(oResourceBundle, "i18n");
// Create view
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.
However, the error message itself will only be displayed when that particular field has focus:
Related Information
Managing UI and Server Messages
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under 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");
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: {showM
</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 }
</mvc:View>
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,
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
# Product list
productListTitle=Artikelliste
stockValue=Lagerbestand Wert
We add the missing texts.
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Data Binding - Step 13 .
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}" />
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/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.getView().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
QuantityPerUnit=Quantity per Unit
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
Preview
Coding
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
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.
In this case, the collision occurs between the syntax of XML and the syntax of the JavaScript-like expression language used by SAPUI5.
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} <= ${/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 (&&).
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under 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.
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]),
// Set item active (so it is clickable) and attach the press event
// handler for showing the details
oUIControl.setType(sap.m.ListType.Active);
oUIControl.attachPress(this.onItemSelected, this);
return oUIControl;
}
});
});
In the App controller, we create a new function called productListFactory. The types of controls returned from this factory function must be limited to
those suitable for inclusion in the items aggregation of a sap.m.List object. In this case, we will return either a StandardListItem or an
ObjectListItem using the following logic:
We decide which type of control to return by checking the current stock level and whether or not the product has been discontinued. This creates the following
possible responses:
1. First, eliminate the minority case.
If the stock level is zero and the product has also been discontinued, then use a StandardListItem with a warning icon and a Product Discontinued
message in the status property. All other possibilities will use an ObjectListItem.
webapp/i18n/i18n.properties
...
# Product Details
...
outOfStock=Out of Stock
webapp/i18n/i18n_de.properties
...
# Product Details
...
outOfStock=Nicht Vorrtig
We add the missing texts to the properties files.
Related Information
Aggregation Binding
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 companys 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
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 SAPUI5 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 Explored app in the Demo Kit under 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 Tutorials):
Prerequisites
Outline of the Steps of Each Tutorial
Downloading Code for a Tutorial Step
Adapting Code to Your Development Environment
Troubleshooting
Preview
Coding
To set up your project for this tutorial, download the files for Step 1 from the Explored app in the Demo Kit under 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 tutorial.
You should have the same files as displayed in the following figure:
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.
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.
Coding
You can view and download all files in the Explored app in the Demo Kit under Routing and Navigation - Step 2 .
webapp/manifest.json
{
"_version": "1.1.0",
"sap.app": {
...
},
"sap.ui": {
...
},
"sap.ui5": {
"_version": "1.1.0",
"rootView": "sap.ui.demo.nav.view.App",
"dependencies": {
...
},
"models": {
...
},
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "sap.ui.demo.nav.view",
"controlId": "app",
"controlAggregation": "pages",
"transition": "slide"
},
"routes": [{
"pattern": "",
"name": "appHome",
"target": "home"
}],
"targets": {
"home": {
"viewName": "Home",
"viewLevel" : 1
}
}
}
}
}
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 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 cant see it in action until you have initialized the router.
Note
As of SAPUI5 version 1.30, we recommend that you define the routing in the manifest.json descriptor file using routes and targets. In older versions of
SAPUI5, 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);
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"/>
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", {
});
});
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
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Routing and Navigation - Step 3 .
webapp/manifest.json
{
...
"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",
"viewLevel" : 1
},
"notFound": {
"viewName": "NotFound",
"transition": "show"
}
}
}
}
}
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.
Conventions
Always configure the bypassed property and a corresponding target
Use the sap.m.MessagePage control to display routing related error messages
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under 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 views controller. However, we are smart enough to anticipate that we might need the same handler function for different
views. DRY (Dont Repeat Yourself) is the right approach for us, so lets 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 dont want to duplicate in our controllers for each page that has a back navigation.
The function checks if there is a previous hash value in the app history. If so, it redirects to the previous hash via the browsers 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 components 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 SAPUI5 there are multiple options to reuse code. We recommend to use a base controller for such helper methods because this allows us to
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
apps 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";
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 browsers 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 browsers 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 browsers 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
Preview
Coding
You can view and download all files in the Explored app in the Demo -kit under 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.
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 apps 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 apps 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/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 SAPUI5 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 controllers 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 controllers onNavBack implementation.
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
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Routing and Navigation - Step 6 .
webapp/view/Home.view.xml
<mvc:View
controllerName="sap.ui.demo.nav.controller.Home"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
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.1.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"
}],
"targets": {
"home": {
"viewName": "Home",
"viewLevel" : 1
},
"notFound": {
"viewName": "NotFound",
"transition": "show"
},
"employees": {
"viewPath": "sap.ui.demo.nav.view.employee",
"viewName": "EmployeeList",
}
}
}
}
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.
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.
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", {
});
});
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
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 browsers or the
apps Back button to get back to the home page.
Related Information
Method and Events for Navigation
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Routing and Navigation - Step 7 .
webapp/manifest.json
{
"_version": "1.1.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"
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"
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}" />
webapp/controller/employee/Employee.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.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 employees 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.
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 dont 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>
Its 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;
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 routes 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
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Routing and Navigation - Step 8 .
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>
<Title text="{i18n>EmployeeIDColon} {EmployeeID}" level="H2"/>
<ToolbarSpacer />
<Link text="{i18n>FlipToResume}" tooltip="{i18n>FlipToResume.tooltip}" press="onShowResume" />
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 views bound context.
The route employeeResume is not available yet, so we will have to add it to our routing configuration.
webapp/manifest.json
{
"_version": "1.1.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/{employeeId}",
"name": "employee",
"target": "employee"
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>
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. SAPUI5 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.
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 dont 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 SAPUI5.
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 SAPUI5 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
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 employees
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.
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Routing and Navigation - Step 9 .
webapp/manifest.json
{
"_version": "1.1.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"
}, {
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
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"
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) {
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, lets 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 wont 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.
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
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Routing and Navigation - Step 10 .
webapp/view/employee/Resume.view.xml
<mvc:View
controllerName="sap.ui.demo.nav.controller.employee.Resume"
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 dont 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>
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"];
} 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
lets create them in our routing configuration.
webapp/manifest.json
{
"_version": "1.1.0",
"sap.app": {
...
},
"sap.ui": {
...
},
"sap.ui5": {
...
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "sap.ui.demo.nav.view",
"controlId": "app",
"controlAggregation": "pages",
"transition": "slide",
"bypassed": {
"target": "notFound"
}
},
"routes": [{
...
}, {
"pattern": "employees/{employeeId}/resume:?query:",
"name": "employeeResume",
"target": "employeeResume"
}],
"targets": {
...
"employeeResume": {
"viewName": "employee.Resume",
"viewLevel" : 4,
"transition": "flip"
},
"resumeTabHobbies": {
"parent": "employeeResume",
"viewPath": "sap.ui.demo.nav.view.employee",
"viewName": "ResumeHobbies",
"viewId": "thisIsMyCustomIdToBeUsedForResumeHobbies",
"controlId": "hobbiesTab",
"controlAggregation": "content"
},
"resumeTabNotes": {
"parent": "employeeResume",
"viewPath": "sap.ui.demo.nav.view.employee",
Note
Each target can define only one parent with its parent property. This is similar to the SAPUI5 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
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Routing and Navigation - Step 11 .
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.
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");
},
});
});
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 lets create it.
webapp/manifest.json
{
"_version": "1.1.0",
"sap.app": {
...
},
"sap.ui": {
...
},
"sap.ui5": {
...
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "sap.ui.demo.nav.view",
"controlId": "app",
"controlAggregation": "pages",
"transition": "slide",
"bypassed": {
"target": "notFound"
}
},
"routes": [{
"pattern": "",
"name": "appHome",
"target": "home"
}, {
"pattern": "employees",
"name": "employeeList",
"target": "employees"
}, {
"pattern": "employees/overview",
"name": "employeeOverview",
"target": ["employeeOverviewTop", "employeeOverviewContent"]
}, {
"pattern": "employees/{employeeId}",
"name": "employee",
"target": "employee"
}, {
"pattern": "employees/{employeeId}/resume:?query:",
"name": "employeeResume",
"target": "employeeResume"
}],
"targets": {
...
"resumeTabNotes": {
"parent": "employeeResume",
"viewPath": "sap.ui.demo.nav.view.employee",
"viewName": "ResumeNotes",
"controlId": "notesTab",
"controlAggregation": "content"
},
"employeeOverview": {
"viewPath": "sap.ui.demo.nav.view.employee.overview",
"viewName": "EmployeeOverview",
"viewLevel" : 2
},
"employeeOverviewTop": {
"parent": "employeeOverview",
"viewPath": "sap.ui.demo.nav.view.employee.overview",
"viewName": "EmployeeOverviewTop",
"controlId": "EmployeeOverviewParent",
"controlAggregation": "content"
},
"employeeOverviewContent": {
"parent": "employeeOverview",
}
}
}
}
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 route-matched events.
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 parents 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.
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 dont need a controller for this 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.EmployeeOverviewContent", {
onInit: function () {
this._oTable = this.getView().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", {
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 Explored app. 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
Region=Region
EmployeeID=Employee ID
Phone=Phone
Employees=Employees
Add the new texts to the properties file.
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Routing and Navigation - Step 12 .
}, {
"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.
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.EmployeeOverviewContent", {
onInit: function () {
var oRouter = this.getRouter();
this._oTable = this.getView().byId("employeesTable");
this._oVSD = null;
this._sSortField = null;
this._bSortDescending = false;
this._aValidSortFields = ["EmployeeID", "FirstName", "LastName"];
this._sSearchQuery = null;
this._oRouterArgs = null;
Preview
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.EmployeeOverviewContent", {
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 tables 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 tables sorting options, you will see that the hash updates accordingly.
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Routing and Navigation - Step 14 .
/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.EmployeeOverviewContent", {
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();
// make sure the dialog does not get closed automatically
oEvent.preventDefault();
}
}
},
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*/);
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. Thats exactly what we wanted.
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under 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>
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.EmployeeOverviewContent", {
...
_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).
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under 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 () {
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.");
});
}
});
});
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.
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under 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.");
});
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 (Build-Measure-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.
Next: Summary
1.3.4.18 Summary
You have now completed the tutorial "Navigation and Routing" for SAPUI5.
You should now be familiar with all major features and APIs for navigation and routing in SAPUI5.
1.3.5 Testing
PUBLIC Page 234 of 244
2014 SAP SE or an SAP affiliate company. All rights reserved.
1.3.5 Testing
In this tutorial we will test application functionality with the testing tools that are delivered with SAPUI5. 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 SAPUI5 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 SAPUI5 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 from the explored app and playing around with it.
Preview
Prerequisites
In addition to the prerequisites that are presupposed for all our tutorials (see Prerequisites), 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.
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 Explored app in the demo kit under 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 Tutorials):
Outline of the Steps of Each Tutorial
Downloading Code for a Tutorial Step
Adapting Code to Your Development Environment
Troubleshooting
Related Information
Testing and Performance Measurement
QUnit Home Page
Preview
Coding
To set up your project for this tutorial, download the files for Step 1 from the Explored app in the Demo Kit under 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 coming with
this tutorial are explained in detail in the Walkthrough tutorial.
You should have the same files as displayed in the following figure:
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 in mimicking a real service and it processes requests with a small delay as a real service would do. This is perfect for realistic
application testing and also helpful for local development tests. It is also 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 can be replaced later by a real service. The service we use has
just one 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. The actual test data containing several mocked 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 SAPUI5
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 easily run the app and show realistic data for testing, even without 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 mimicking 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 explain more details
about the unit test setup later.
Integration Tests
Integration tests are written in OPA5 a tool for integration testing that is included in SAPUI5 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 there is already a worklist journey included that checks if the table of posts is displayed properly. We will
explain more details about the integration test setup later.
Other quality-related features of the app
The app is set up according to best practices and already contains many helpful features. The most important ones are named here.
Separation of concerns (MVC)
All artifacts are located in either the model, view, or controller folder of the app. The apps component and its descriptor is configuring which
of those MVC artifacts to load. the configuration is controlling the navigation flow of the app.
Separation of productive and non-productive code
All non-productive 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, users should always get 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 we have a running prototype that we can further extend with additional tests and features. Make sure 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
Lets first take a look at best practices for testing apps written in SAPUI5. 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 a unit and integration testing infrastructure 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.
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.
When we think about application testing, we want to automate as many testing steps as possible. When we immediately write a test for all the features that we
implement then we can greatly reduce manual testing efforts that are time consuming and cumbersome. Whenever we change something later, we can simply
run the existing tests and see if the functionality is still working as expected.
The two testing tools that are included in SAPUI5 are QUnit for unit testing and OPA5 for integration testing. The foundation for our testing pyramid are the unit
tests and they should validate the most important logic of our app. On top, we can write integration tests for more interaction-related functionality like
interacting with UI elements of the app.
There might still be things that are hard to test with these client-side testing frameworks. Certain features might require a more sophisticated system test, like
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.
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 in this tutorial it is a natural result.
Conventions
Write unit tests in QUnit for more logic-related functionality
Write integration tests in OPA5 for user interaction
Separate productive and non-productive code within the app (webapp, test folder)
Provide a local test page that triggers the app in test mode with mock data (test/mockServer.html)
Related Information
App Templates: Kick Start Your App Development
Worklist Template
Testing and Performance Measurement
Unit Testing with QUnit
Integration Testing with One Page Acceptance Tests (OPA5)
Mock Server
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.
Preview
Figure 1: The unit test will initially fail as the implementation is not provided yet
Coding
You can view and download all files in the Explored app in the Demo Kit under Testing Apps - Step 2 .
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.
Lets 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 Explored app in the Demo Kit under Testing Apps- Step 2 .
webapp/model/formatter.js
sap.ui.define([
"sap/m/Text"
], function (Text) {
"use strict";
return {
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.
SAPUI5 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,
}
);
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.
Lets 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.
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.
Thats 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.
Related Information
QUnit Testing Fundamentals
QUnit Home Page
Preview
Coding
You can view and download all files in the Explored app in the Demo Kit under Testing - Step 3 .
webapp/model/formatter.js
sap.ui.define([
"sap/m/Text"
], function (Text) {
"use strict";
return {
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) {
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.
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.