Beruflich Dokumente
Kultur Dokumente
Applies to:
SAP Fiori Reference Apps
SAP Web IDE 1.14
SAP Netweaver 7.40 SPS 10
Summary
This document provides an overview of the technical aspects of the SAP Fiori Reference App Manage
Products, which has been implemented in namespace nw.epm.refapps.ext.prod.manage.
.
Author Bio
Dr. Stephan Naundorf is a Development Architect in the SAP Global Design team of SAP SE.
Ian Robert Taylor is a Senior Developer in the SAP Global Design team of SAP SE.
Christian Kolbowski is a Development Architect in the SAP Global Design team of SAP SE.
Table of Contents
Contents
Features of the App ............................................................................................................................................ 3
Draft Pattern .................................................................................................................................................... 3
Implementation ................................................................................................................................................... 4
Main Building Blocks ....................................................................................................................................... 4
Lifecycle .......................................................................................................................................................... 5
Error Handling ................................................................................................................................................. 6
Busy handling .................................................................................................................................................. 7
Edit View ......................................................................................................................................................... 8
Further Topics ............................................................................................................................................... 10
Backend Implementation .................................................................................................................................. 12
Development Environment ............................................................................................................................ 12
Overview of the Gateway Service ................................................................................................................. 12
Service Implementation................................................................................................................................. 13
OData Requests............................................................................................................................................ 14
Copyright........................................................................................................................................................... 19
This snapshot is actually taken with a small delay. Normally the draft is stored when the user moves from
one field to another.
If the dirty flag of a draft is false, the draft is deleted without notice. That means that no data-loss dialog
appears when the user cancels the editing session for a non-dirty draft and lost drafts are ignored when they
are not dirty.
Note that the ID of a draft is identical to the ID of the corresponding product. Hence, a draft created for
editing reuses the ID of the product and vice versa a newly-created product reuses the ID of the draft that
was used to create it.
Implementation
The structure of the app has been adapted to the SAPUI5 Master-Detail Application template. However, note
that this app covers a much more complex scenario than the template. Therefore, additional concepts have
been added.
On the other hand, some pieces of code that are not used in this app have been removed in order to keep
the code as simple as possible3.
Main Building Blocks
According to the general rule for Fiori Apps, this app is started using class
nw.epm.refapps.ext.prod.manage.Component, which inherits from sap.ui.core.UIComponent. As usual, this
class contains the metadata for routing, OData access, icons, and so on. Moreover, the class possesses an
init-method which creates some global models used by the app and initializes the ApplicationController (see
below) and the router.
Application Controller
The implementation of the app is delegated to class nw.epm.refapps.ext.prod.manage.controller.Application,
which serves as (singleton) controller for the app. The single instance of this class is added to the application
model as property applicationController (see below). Thus, it can be easily accessed by all view controllers
(more generally: by all pieces of code that have access to the component).
Note that the state handling of the app is rather sophisticated, so it turns out to be easier to have one central
instance responsible for managing this state handling compared to the approach of distributing the state
handling of the app across the single views.
Obviously it would have been possible to enhance the component object itself such that it could serve as an
application controller. However, this object inherits from sap.ui.core.UIComponent. Thus, each property or
method added to the controller would potentially produce a conflict with any property or method of the base
class that has the same name by chance.
The topmost views
The (split-) app as a whole is hosted by view nw.epm.refapps.ext.prod.manage.view.App.
Master and detail view are implemented by nw.epm.refapps.ext.prod.manage.view.S2_ProductMaster and
nw.epm.refapps.ext.prod.manage.view.S3_ProductDetail.4 Note that these views are attached to the
corresponding routes in the components metadata. Therefore, instantiation of the views is automatically
performed via the router.
As already mentioned, all views have access to the application controller via the application model. On the
other hand, master view and detail view register with the application controller using methods
registerMaster() and registerDetail().5
One example would be method _createMetadataPromise in class Component.js from the template, which is
not used in this app.
4
According to the structure given by the template, the corresponding controllers (as for all views) can be
found in folder controller.
5
The app view is simply retrieved from the component. Thus, the application controller has access to all
three topmost views (or their controllers) via attributes _oMainView, _oMasterController, _oDetailController.
Standard models
Class nw.epm.refapps.ext.prod.manage.model.models.js was copied from the template and adapted to this
app. This class provides the code for creating the i18n-model, the device model (a JSON model that
provides access to information about the device being used), and the v2-OData model used in this app.
Component.js uses this facility and attaches the models to the component (as the named models i18n,
device, and as the default model).
The OData model uses OData service EPM_REF_APPS_PROD_MAN_SRV. Note that the OData service
has distinct entity types for products and product drafts (called Product and ProductDraft). An alternative
solution could model both entities in one entity type. The distinction between drafts and real products would
be implemented using a suitable property.
Application Model
The application controller instantiates an additional JSON model and attaches it to the component (with
name appProperties) such that it can be accessed by all views.
This model contains data describing the global state of the app (for example, whether the app is currently in
display or in edit mode).
This model is accessed declaratively and programmatically in various places.
Navigation Manager
nw.epm.refapps.ext.prod.manage.controller.NavigationManager is a helper class of the application controller.
It is responsible for all navigation tasks. In particular all interaction with the router (after its initialization) is
handled by this class. This means that this class is the only one listening to the router events and it is the
only one calling the navTo-method of the router.
Lifecycle
Global Lifecycle
The global lifecycle of the app is controlled by the application controller and its helper class, the navigation
manager.
In particular, the application controller listens to the standard event onMetadataLoaded and the navigation
manager listens to the standard event onRoutePatternMatched.
Moreover, all navigation that is triggered programmatically is performed using the navigation manager (see
method _executeNavigation() and the public methods utilizing this6). Note that attribute
_bNavigationByManualHashChange is used to distinguish between route changes that are caused externally
(for example, the user changing the URL-hash manually) and those caused programmatically.
In addition, the application controller also listens to the following specific events:
Deletion of one or more products (see deleteListener)
handleLostDraft (normally only during startup)
Lifecycle of the Main Views
The topmost views are initialized by the router as normal. This triggers the lifecycle method onInit() of the
corresponding controllers.7 As already stated, the controllers of the main views register with the application
controller at this point in time.
The application controller contains wrapper methods for these public methods such that all other classes
can access them via the application controller without needing to know the helper class.
7
Note that at this point in time the global models have not yet been attached to the views. Therefore, the
models are always accessed via the component here.
And each of these unsuccessful attempts is displayed to the users using the showErrorMessage utility.
When these operations have finished successfully, users might be asked to deal with a lost draft. This
would probably be irritating if they have already started working with the app. Therefore, it seems appropriate
to set the screen to busy during startup.
10
Exception: The deletion of a draft is done totally asynchronously. Attribute _oWhenNoDraft in class
nw.epm.refapps.ext.prod.manage.model.Products is used to ensure that the deletion of a draft does not
interfere with other operations.
Moreover, the control which is used for the list itself has a built-in busy handling. In order to avoid duplicate
busy indications here, property enableBusyIndicator of the list is set to false.
The idea for pushing up the busy state to the view level is that otherwise the list operations, such as filtering,
would still be open while the list is loading. These operations seem to be so closely connected to the list itself
that it was decided to lock them while the list is loading.
Busy state of the detail area
The edit view cannot be set to busy on its own. All operations requiring a busy state here result in the whole
app becoming busy. Therefore, the busy state of the detail area is implemented in view
nw.epm.refapps.ext.prod.manage.controller.ProductDisplay. The busy state of this view is bound to (the
negation of) property dataLoaded of the local view properties model, since the view is supposed to be busy
as long as no data is there to display.
Again we want to avoid duplicate busy indications. Therefore, the busy state of the app is bound to an
expression ensuring that the view is not busy while the whole app is busy at the same time.11
Busy indicator delay
In most scenarios backend calls are fast. User experience would suffer if busy indicators appear for a short
time with every action. Therefore, SAPUI5 controls do not only possess an attribute busy indicating whether
the control is busy but also an attribute busyIndicatorDelay, which defines a time period that the app waits for
until the busy indication is actually shown.
This attribute possesses a reasonable default. In this app, we sometimes switch between this default and a
value of 0. Thereby, we rely on the fact that the default value is used when the attribute busyIndicatorDelay
is set to null.12
For simplification, the delay is set to 0 during startup and whenever a change operation is performed. This is
acceptable because these operations are rare and the user expects them to last a certain time.
Therefore, the delay is set to the fixed value 0 in the definition of nw.epm.refapps.ext.prod.manage.view.App.
Master and detail view should normally have the default delay. However, when the busy state follows
immediately after the whole app being busy, it should be 0 in order to avoid flickering of busy states.
Therefore, the corresponding delays are bound to properties detailBusyIndicatorDelay and
masterBusyIndicatorDelay of the global application model, which are initialized as 0 (startup phase) and set
to null when the corresponding data has been loaded once.
When the whole app is set to the busy state again (change operations) detailBusyIndicatorDelay is set to 0
once more because the detail area has to be updated again after the change has been executed.13 It will be
set back to null when the detail screen is filled the next time.
It might seem that the same is necessary for the master view. However, the request for updating the master
list is actually batched with the request performing the change operation. Therefore, the master view itself
does not become busy at all in these scenarios.
Edit View
Introduction
The edit view is an important aspect of the SAP Fiori Reference App Manage Products because it
demonstrates how to provide help to the user on entering valid data. In particular, value lists are presented to
11
Moreover, the expression ensures that the view is not busy in the exceptional situation that loading of
metadata is unsuccessful. Obviously, in this case the data are not loading although property dataLoaded is
false.
12
This assumption is true for views, but might be false for other controls overriding the generic busy handling
of SAPUI5. Therefore it might be suitable to retrieve the default value programmatically and switch between
this value and 0 from then on when handling the busy state with finer granularity.
13
the user in the form of either ComboBox or Select controls. These are used according to the UX Guidelines
so that for fields where the number of possible values does not exceed 20, the Select control is used. Where
there are more than 20 values, the ComboBox control is used. Whereas the Select control only allows the
user to pick a value from a list of possible values, the ComboBox allows the user to start typing the value and
thereby limits the list of possible values.
User Changes
A user selects to edit an existing product by choosing Edit on the view S3_ProductDetail. The contents of the
chosen product are copied to a Draft. Changes made by the user in the input fields are saved to the Draft
when the cursor leaves the respective input field. The event change on the input field controls sap.m.Input
and sap.m.Select and event selectionChange for the control sap.m.ComboBox are used to trigger an update
of the Draft. The Draft is updated using method submitChanges from the v2 OData model and the changed
field is written to the Draft in the backend system via the OData call Merge, called by the OData model.
Select Value Lists
The fields for the units of Length, Width and Height and for the Weight unit present the possible values using
the control sap.m.Select because there are less than 20 possible values to choose from. The values of the
dimension units are obtained from the OData service from the entity set DimensionUnits and the weight units
from the OData service entity set WeightUnits.
ComboBox Value Lists
Similarly to the Select value lists, the value lists for the fields that use ComboBox to display the possible
values also use OData service entity sets. For example, the Currencies entity set for the permitted currency
codes.
Category Value List
There is a unique set of possible Category Values for each Main Category Value. Changes to the Main
Category field therefore need to define a new list of Category Values according to the Main Category. A filter
is defined on the OData binding of the field Category in method _setCategoryFilter in the Edit controller so
only the relevant categories are shown. Additionally, the Category is set to empty when the user selects a
new Main Category.
Supplier Value List
The Supplier field has two variants for providing possible values. The entity set Suppliers provided by the
OData service is used to provide a Type Ahead suggestion using showSuggestion="true" in the XML part of
the Edit view. As a user types the name of the supplier, a proposal list is provided by the sap.m.Input control
from which the user can select the corresponding entry.
Additionally, a search help is provided for Supplier. When the user chooses the icon consisting of two
overlapping squares at the end of the input field, a search dialog appears in which the user can search for,
then select the entry required. In this case, the list of possible Supplier values is obtained by annotations in
the OData service. The path Product/SupplierName retrieves the possible SupplierName and SupplierId
values.
In order to make use of this search help, it is currently necessary to create the class
sap.ui.comp.providers.ValueHelpProvider in the controller for the edit screen. This class then provides the
search help dialog. In future releases of SAPUI5, the search help will be facilitated merely from the field
definition in OData without requiring any implementation in the app.
Error Checking of Field Changes
As stated, the changes are saved to the Draft in the back end after each field is changed. The SAP Gateway
and the OData service implementations in the back end make validation checks on the content of fields, for
example the Price and Weight fields.
In the event that the field contents do not match the definitions in the back end, the OData call returns an
error message. We assume that the error message relates to the field that was changed. The error message
is obtained from the OData response call. Since OData batch requests are used, the error messages may
appear in a batch call where the overall status is Success and thus you must check method
_submitChanges in nw.epm.refapps.ext.prod.manage.model.Products for possible error messages.
Error messages found are displayed on the Edit screen using the value state text of the input field in which
the last change was made and the value state is set to Error, resulting in the field being highlighted in red.
Further Topics
Modularization
We have already discussed the main components of the app and how they interact. However, there are
some other classes that take over certain tasks.
The most prominent of them is nw.epm.refapps.ext.prod.manage.model.Products. A singleton instance of
this class is created by the application controller and can be accessed using method getODataHelper().
This class is responsible for performing all explicit OData calls needed in the app. To remove objects, it uses
helper class nw.epm.refapps.ext.prod.manage.model.RemoveService, which is instantiated on demand.
The handling of searching, sorting, filtering, and grouping in the master list is delegated to the following
classes:
nw.epm.refapps.ext.prod.manage.controller.TableOperations
nw.epm.refapps.ext.prod.manage.controller.SubControllerForFGS
Note that the services provided by the first class are agnostic with respect to specifics of the entity handled
by this app (products) and may therefore be reused by any other app using the master-detail pattern.
The implementation of the Share button that is available on the DispIay and Edit screens is done by fragment
nw.epm.refapps.ext.prod.manage.view.ShareSheet and class
nw.epm.refapps.ext.prod.manage.controller.SubControllerForShare.
Adjusting the Master List
One of the most delicate tasks of the app is to bring the master list into the correct state. In particular this
means the following, as appropriate:
Ensure that the correct list item is selected
Ensure that the selected list item is scrolled to
Ensure that a list item is selected at all
Which one of these actions really should be taken depends on several factors. Here are some examples to
illustrate this:
Normally, the list item corresponding to the product currently shown in the detail area should be
selected in the master list. This is not true in multi-select mode.
10
Moreover, note that there are cases in which there is no list item that corresponds to the product
currently shown in the detail area.14
In certain cases, the master list should scroll to the list item corresponding to the product currently
shown in the detail area. This is particularly true when the app is started with a URL that specifies
the product to be displayed. In other cases, the master list is supposed to hold its position. This is
particularly true when the user manually selects an item in the master list.
In certain scenarios, the list item to be selected is predetermined (such as when the user starts the app with
a URL that specifies a certain product). In this case, the empty page appears if the product is not available.
In other scenarios, we have a preference for the item to be selected (such as when the user deletes the
currently selected product. In this case, the next item in the list should preferably be selected if still
available).
Finally, we have scenarios in which just any item (the first one in the list) is to be selected (such as when the
user starts the app with no specific product selected and no lost draft available).
Method findItem() in the controller of S2_ProductMaster is responsible for determining the item to be
displayed, if necessary. Note that this method is called each time the list is updated.
14
For example, this can happen if users apply a filter to the master list that excludes the product they have
selected beforehand.
11
Backend Implementation
The OData service for the Manage Products app was modelled in a Gateway project. The classes,
model, and model design aspects are outlined here.
We assume that you are already familiar with the basic concepts of OData services. For more information
about OData, see the specification: OData V2
You should not use the technical names provided in the examples here but base your names on the
appropriate naming conventions for your product or app.
Development Environment
CL_ EPM_REF_APPS_PROD_M_DPC: Base class for data provisioning providing a default implementation
for all service artifacts raising a not-implemented exception. This class is regenerated each time that the
service is regenerated in the service builder.
CL_ EPM_REF_APPS_PRD_M_DPC_EXT: Derived from the base class, where methods of the data service
are overwritten (for example, method REVIEWS_CREATE_ENTITY) this means not-overwritten methods
show the default behavior not implemented. This class is not affected by regeneration of the service in the
service builder. For the Manage Products service, all CRUD operations and function imports are handled in
the data provider class. All the data retrieval is handled via the SADL runtime by making use of core data
services.
CL_EPM_RAMP_DRAFT: Helper class for the handling of CRUD operations on product draft instances.
Model Provider Classes
CL_ EPM_REF_APPS_PROD_M_MPC: Contains the EDM model data generated from the service builder.
This class is regenerated each time that the service is regenerated in the service builder.
CL_ EPM_REF_APPS_PROD_M_MPC_EXT: Implements changes to the service model not modeled in the
service builder. This class is not affected by the regeneration of the service in the service builder.
The implementation contains the necessary annotations for the different value help lists used in the app.
12
MainCategory
SubCategoryMainCategories
0..n
SubCategory
ProductDraftImageDrafts
0..n
1
ProductsSupplier
ProductsSubCategory
ImageDraft
Supplier
0..n
ProductDraft
0..n
0..1
0..1
Product
Service Implementation
While all CRUD operations and function imports of the service are handled in the corresponding method
implementations of the data provider class CL_ EPM_REF_APPS_PRD_M_DPC_EXT, the data retrieval is
handled by the SADL runtime by making use of core data services. For each of the service entity sets, this
means that their corresponding GetEntity and GetEntitySet methods are not redefined in the data provider
class; instead the SADL runtime is called directly from the data provider base class method.
For example, for entity set Products, the DPC base class method implementation looks as follows:
if_sadl_gw_dpc_util~get_dpc( )->get_entityset(
EXPORTING io_tech_request_context = io_tech_request_context
IMPORTING et_data
= et_entityset
es_response_context
= es_response_context ).
Taking entity set Products as an example, in the project service implementation view of the Gateway Service
Builder, it is directly mapped to the corresponding core data service (CDS) view
SEPM_C_RAMP_PRODUCT.
You can find all corresponding CDS views in the packages dictionary section under ABAP DDL sources.
Please note that you need ABAP in Eclipse to display their content.
13
OData Requests
This section describes the start screen of the EPM Manage Products app with the corresponding OData
requests required by the SAPUI5 controls.
GET Operations
1. The data for the product list is retrieved using this call:
GET Products?$skip=0&$top=20&$orderby=Name asc&$select=Id%2cImageUrl%2cName%2cPrice
%2cCurrencyCode%2cSubCategoryName%2cMainCategoryName%2cQuantityUnit%2cStockQuantity
&$inlinecount=allpages
2.
From the first calls result, the property ImageUrl points to the location in the MIME repository where
the corresponding product image is stored for each result set entry. The images are then retrieved
using separate GET requests:
GET /sap/public/bc/NWDEMO_MODEL/IMAGES/HT-2001.JPG
3. In the master list, the lead selection is automatically set to the first list item. To display the product
information in the detail area, a call to the product entity set is performed.
GET Products('HT-2001')?$expand=Supplier
4. With the same call, an $expand to the Supplier entity retrieves all supplier details displayed in the
linked business card.
14
5. In parallel, a call to the ProductDrafts entity set is made in order to determine whether users were
editing a product the last time they accessed the app:
GET ProductDrafts
If the browser session was terminated unexpectedly, the next time users start the app they are asked
whether they want to continue editing this draft:
Choosing Resume closes the dialog and displays the Edit screen.Choosing Discard triggers a delete
request to the entity set ProductDrafts:
DELETE ProductDrafts('HT-2000')
6. The master list provides a search field. When entering a search pattern and choosing Enter, a backend
search is performed for the product name.
GET Products?$skip=0&$top=20&$orderby=Name asc&$select=&search=Beam&$inlinecount=allpages
7. The master list footer toolbar offers buttons for sorting, filtering, and grouping.
Group by produces the same OData request as list sorting, additionally the list control groups the result
using the respective group criteria.
Example of sorting by Price and Name in ascending order:
GET Products?skip=0&$top=20&$orderby=Price asc, Name asc&$select=
15
Draft Handling
New Draft
8. The + button triggers the creation of a new product. By passing an empty payload to the back end, a
new draft product is created.
POST ProductDrafts
Payload: {"ProductId":""}
With this information, a GET request is triggered, retrieving the new draft entity:
GET ProductDrafts('HT-2001')?$expand=Images
Edit Draft
9. Editing of an existing product triggers function import EditProduct, passing the respective product ID:
POST EditProduct?ProductId='HT-2001'
In the back end, the system first checks whether a draft entity already exists for the requested product;
in that case it checks whether the user that wants to edit it is the draft owner (then the draft is
returned, if not an exception is triggered). Then a deep copy of the existing product is created and
stored in the draft persistency (DB tables SEPM_RAMP_PDDRFT for the draft product,
SEPM_RAMP_IMDRFT for the draft product images).
In a second get call, entity set ProductDrafts is addressed with the respective product ID (as mentioned
in the UI documentation, the existing product ID is reused for the edit case too).
GET ProductDrafts('HT-2001')?$expand=Images
A second call to entity set SubCategories retrieves the value list for the current main categories, and
finally a call to entity set QuantityUnits retrieves the value list for the unit of measures.
GET SubCategories?$skip=0&$top=100&$orderby=Name%20asc&$filter=startswith(MainCategoryName,
TV Video HiFi)&$inlinecount=allpages
GET QuantityUnits?$skip=0&$top=20&$orderby=Shorttext%20asc
Depending on the edit state, when the user chooses Cancel, the draft instance is either discarded
without further notice (edit state unchanged) or the user is warned that his or her entries will be lost.
While editing, changes to the product are implicitly saved each time the input-field focus is changed. In
the payload of the call, all collected changes made to the entity are passed (this is handled by SAPUI5
automatically).
MERGE ProductDrafts('HT-2001')
In the back end, the product is again read from the draft persistency and an update is triggered against
the EPM persistency where the original product is persisted. If this is successful, the entry in the draft
persistency is deleted.
The OData response now contains the data of the product just updated with the same shared ID.
10.
16
The next two calls retrieve the updated list of products and the details of the new lead selection in the
product list, which is set automatically:
GET Products?$skip=0&$top= $orderby=Name asc&$select=
GET Products('HT-2001')
11. Copying the product currently selected triggers the following requests:
POST CopyProduct?ProductId='HT-2001'
In the response, you receive the URL to the new draft product:
/sap/opu/odata/sap/EPM_REF_APPS_PROD_MAN_SRV/ProductDrafts('EPM-000002')
The response ID is used to query the product draft entity. Since we have a deep copy including all
images, an expand to entity Images is done.
GET ProductDrafts('EPM-000002')?$expand=Images
The response includes an image URI that points to the ImageDraft entity set:
/sap/opu/odata/sap/EPM_REF_APPS_PROD_MAN_SRV/ImageDrafts(guid'005056A7-004E-1EE4-B1E402BC1F3F70BF')
Copying a product basically runs through the same routine as creating a product, except that additionally
the ID for the product to be copied is passed with the request, which is used to select the existing
product data used for the copy.
17
Related Content
For more information, visit the links below:
Fiori Reference Apps on SCN
http://scn.sap.com/docs/DOC-59963
http://scn.sap.com/docs/DOC-55465
http://marketplace.saphana.com/Industries/CrossIndustry/SAP-River-Rapid-DevelopmentEnvironment/p/3334
Detailed description:
Reference App - Shop
http://scn.sap.com/docs/DOC-60841
Detailed description:
Reference App - Approve Purchase Orders
http://scn.sap.com/docs/DOC-60846
http://scn.sap.com/docs/DOC-61464
18
Copyright
2015 SAP SE SE or an SAP SE 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 SE Group) for informational
purposes only, without representation or warranty of any kind, and SAP SE Group shall not be liable for
errors or omissions with respect to the materials. The only warranties for SAP SE 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 SE and other SAP SE 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
http://www.sap.com/corporate-en/legal/copyright/index.epx#trademark
for additional trademark information and notices.
19