第 264 页 / 共 465 页
7 Developing a Freestyle Application
assigned frontend Transaction PFCG business roles of the user. In a SAP Gateway hub
scenario, the frontend Transaction PFCG roles containing the relevant business cata-
logs, target mappings, and groups are assigned to the user on the frontend or hub
system. To generate the SemanticObject OData annotation, the field must be anno-
tated with @Consumption.semanticObject:<SEMANTIC_OBJECT> on the CDS data defini-
tion language (DDL) level. Like the UI annotations, the annotation is part of the
separate annotation model provided by the SAP Gateway Catalog Service when gen-
erating an OData service using Auto-Exposure or the Reference Data Source (RDS)
scenario.
Figure 7.3 Smart Link and Its Navigation Popover Containing Links to Related Applications
with the Annotated Semantic Object
Note
The sap.ui.comp.navpopover.SmartLink control will only work in the SAP Fiori launch-
pad and not when testing the application locally in the SAP Web IDE. The user will
only see navigation links and get access to applications that are part of his assigned
frontend Transaction PFCG business roles and whose semantic object defined in
their target mappings match the semantic object defined for the field. The available
target mappings for a user, containing the semantic objects and actions a user has
authorization for, are fetched during startup of the SAP Fiori launchpad.
7.1.3 Smart Form
The sap.ui.comp.smartform.SmartForm control, in combination with SmartField controls,
lets you create a form with minimal effort. It contains a group (sap.ui.comp.smartform.
Group) aggregation that contains a GroupElements (sap.ui.comp.smartform.GroupElement)
264
| Note The sap.ui.comp.navpopover.SmartLink control will only work in the SAP Fiori launch- pad and not when testing the application locally in the SAP Web IDE. The user will only see navigation links and get access to applications that are part of his assigned frontend Transaction PFCG business roles and whose semantic object defined in their target mappings match the semantic object defined for the field. The available target mappings for a user, containing the semantic objects and actions a user has |
| authorization for, are fetched during startup of the SAP Fiori launchpad. |
第 265 页 / 共 465 页
7.1 Smart Controls
aggregation. A GroupElement is a combination of a label and different controls associ-
ated with this label, usually a SmartField. Listing 7.2 shows the general structure of a
SmartForm definition, including the Group, GroupElement, and SmartField controls.
<SmartFrom title=”Smart Form Title”>
<Group label=”Group Label”>
<GroupElement>
<SmartField value=”{ODATA_ENTITY_TYPE_PROPERTY}”/>
</GroupElement>
</Group>
<Group>
…
</Group>
</SmartForm>
Listing 7.2 Element Structure of a SmartForm Consisting of Group, GroupElement, and
SmartField Controls
Moreover, the SmartForm contains a layout aggregation that can be used to configure
the ResponsiveGridLayout, which is internally used by the SmartForm to align its con-
trols in a responsive way, depending on the available space. The layout organizes its
child controls in a 12-column row. Using the properties of the control, it’s possible to
specify how many columns the controls should consume based on the available
screen size. Listing 7.3 shows an example on how the sap.ui.comp.smartform.Layout
control can be used with the layout aggregation to configure the layout of the Smart-
Form.
<SmartForm>
<layout>
<Layout emptySpanL="1" emptySpanM="0" labelSpanL="3" labelSpanM="3"/>
</ layout>
</SmartForm>
Listing 7.3 Layout Example for the SmartForm
The emptySpanL="1" attribute specifies that on a large screen, one grid cell at the end
of each line will be empty. On a medium size screen, you’ll need all the space, so set
it to emptySpanM="0". On large and medium size screens, the label control should not
take up more than 3 columns of the 12-column grid space (labelSpanL="3" and
labelSpanM="3").
265
第 266 页 / 共 465 页
7 Developing a Freestyle Application
Another feature of the SmartForm control is the possibility to enable a Check button
for the form by setting checkButton="true". If you click this button, the values of the
smart fields contained in the form will be checked against their OData metadata
annotations. Figure 7.4 shows an example of a validation triggered by the Check but-
ton. The PurchaseDocument property is marked as mandatory by the Nullable=”false”
attribute in the OData metadata annotations. Hence, if the smart field is checked with
an empty value, it’s marked in red. When the user clicks on the field, the correspond-
ing error message is shown.
Figure 7.4 Example Validation of a Mandatory Field Triggered by Clicking the Check
Button
7.1.4 Smart Table
The sap.ui.comp.smarttable.SmartTable control is a wrapper around SAPUI5 tables.
Like the other smart controls, it analyzes the OData service’s metadata annotations
($metadata) and the UI annotations provided by a separate annotation model and the
Catalog Service of the SAP Gateway hub (/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/
Annotations(TechnicalName=’<ANNOTATION_MODEL>’,Version='0001')/$value/). It ren-
ders a table for a specific OData entity set and makes it possible to create tables with
just a view lines of XML. Because the SmartTable control is also used in the SAP Fiori
elements list report template, basically the same UI annotations can be used.
Figure 7.5 shows a part of the UI.LineItem annotations and how they are interpreted
by the SmartTable control. Just as in the SAP Fiori elements list application, the @UI.
lineItem annotation must be used on the CDS DDL level to specify the default visible
columns of the table and their sequence. The criticality of the OverallPrice and
ApprovalRequired columns is indicated by the OverallPriceCriticality field. This
information is also provided by the line item annotation.
266
第 267 页 / 共 465 页
7.1 Smart Controls
Figure 7.5 Example of How the UI.LineItem Annotations Are Interpreted by the SmartTable
Control
If you want to hide certain fields from the UI but not completely from the entity type
to be able to use it, for instance, as a criticality indicator, you have to annotate them
with the @UI.hidden annotation. Listing 7.4 shows how this can be done for the Over-
allPriceCriticality field as we don’t want to make this field available in the table
field catalog but need it as a technical field to indicate the criticality.
@UI.hidden: true
OverallPriceCriticality;
Listing 7.4 Hide a Field from the UI but Not from the OData Entity Type
Defining a smart table in a SAPUI5 XML view is quite simple. Listing 7.5 shows the
minimal property values required for rendering the table. The smartTable prefix is
defined for the sap.ui.comp.smarttable namespace (xmlns:smartTable="sap.ui.comp.
smarttable"). The entitySet defines the OData entity set that the table will be bound
to. The header attribute sets the text of the table header, and the tableType attribute
specifies the table type to be used—in our case, a ResponsiveTable. By setting the
267
第 268 页 / 共 465 页
7 Developing a Freestyle Application
enableAutoBinding attribute to true, data for the specified entity set will be requested
immediately from the OData service deployed on the SAP Gateway hub system when
the table is rendered.
<smartTable:SmartTable entitySet="Z_C_PurchaseDocumentLrp" header=
"Purchase Documents" enableAutoBinding="true" tableType="ResponsiveTable" />
Listing 7.5 SmartTable Definition inside a SAPUI5 XML View
Usually, a SmartTable is used in combination with a SmartFilterBar. The two controls
are connected using the id of the SmartFilterBar. Therefore, the SmartTable must
define the SmartFilterBar’s id using its smartFilterId attribute.
7.1.5 Smart Filter Bar
The sap.ui.comp.smartfilterbar.SmartFilterBar control analyzes OData metadata
and UI annotations and renders a FilterBar control that can be used to filter a Smart-
Table. Figure 7.6 shows some of the metadata and UI annotations interpreted by the
SmartFilterBar control. The initial displayed fields in the expanded filter bar must
be annotated with the OData UI.SelectionField annotation (@UI.selectionField on
the CDS DDL level). In the example, the PurchaseDocument, Priority, Status, and
PurchasingOrganization fields were annotated as selection fields. Which kind of value
help is rendered for a selection field depends on the sap:value-list attributes of the
respective entity type property; for instance, a dropdown value help will be rendered
for the Priority field because its sap:value-list attribute has a fixed-values value.
The PurchasingOrganization property’s sap:value-list attribute is set to standard, so
a standard value help will be rendered.
In a SAPUI5 XML view, the SmartFilterBar control can be declared as shown in Listing
7.6. The smartFilterBar prefix is defined for the sap.ui.comp.smartfilterbar name-
space (xmlns:smartFilterBar=" sap.ui.comp.smartfilterbar"). The entitySet attri-
bute defines the OData entity set whose metadata is used to render the smart filter
bar. The persistencyKey defines the key that is used for storing personalization data,
for instance, a filter variant that must be used periodically. Although the entity set
might support free text search indicated by the sap:searchable=”true” attribute, a
free text search field isn’t rendered automatically. If it will be enabled in the smart fil-
ter bar, the enableBasicSearch attribute must be set to true explicitly.
<smartFilterBar:SmartFilterBar id=
"purchaseDocumentsSmartFilterBar" entitySet="Z_C_
268
第 269 页 / 共 465 页
7.1 Smart Controls
PurchaseDocumentLrp" persistencyKey=
"purchaseDocumentsFilterBarKey" enableBasicSearch="true" />
Listing 7.6 SmartFilterBar Definition inside a SAPUI5 XML View
Figure 7.6 Some of the Metadata and UI Annotations Analyzed by the Smart Filter Bar
If you want to completely remove a field from the smart filter bar’s set of selection
fields, you can do that by annotating the field with @Consumption.filter.hidden: true
on the CDS DDL level (see Listing 7.7). On the OData service level, this will generate the
OData UI annotation UI.HiddenFilter for the respective field.
@Consumption.filter.hidden: true
PurchaseDocumentImageURL
Listing 7.7 Hiding a Field from the Set of Available Selection Fields in the Smart Filter Bar
Note
The smart table as well as the smart filter bar control support variant management
as soon as you provide a unique key via their persistencyKey property under which
the settings can be stored. The variants will be stored in the layered repository of the
SAP Gateway hub frontend system. If you want to store smart table and filter bar set-
tings in a single variant, you can use the smart variant management control to
enable page variant management. You can find further information under Smart
Variant Management in the SAPUI5 SDK documentation at http://bit.ly/2OPBpfp.
269
| Note The smart table as well as the smart filter bar control support variant management as soon as you provide a unique key via their persistencyKey property under which the settings can be stored. The variants will be stored in the layered repository of the SAP Gateway hub frontend system. If you want to store smart table and filter bar set- tings in a single variant, you can use the smart variant management control to enable page variant management. You can find further information under Smart |
| Variant Management in the SAPUI5 SDK documentation at http://bit.ly/2OPBpfp. |
第 270 页 / 共 465 页
7 Developing a Freestyle Application
7.2 Application Development with the SAP Web IDE Full-Stack
In this section, we’ll develop a small SAPUI5 freestyle application consisting of a
smart form for creating purchase documents and a smart filter bar and table for dis-
playing purchase documents. We’ll develop the frontend part of the application in
the SAP Web IDE full-stack and reuse the OData service developed in Chapter 4 for the
SAP Fiori elements list report and object page. We’ll guide you through all of the nec-
essary steps for developing the application, including setting up the OData service
connectivity and navigation in the app descriptor (manifest.json) as well as creating
an OPA5 integration test covering the whole create and display scenario.
7.2.1 Setting Up an OData Service
We’ll start the app development by first creating the initial project structure and set-
ting up the OData service connection to the backend. To create a new freestyle
SAPUI5 project in the SAP Web IDE full-stack, choose File (cid:2) New (cid:2) Project from Tem-
plate from the top-level menu bar. In the initial Template Selection step of the project
creation wizard shown in Figure 7.7, select the SAPUI5 Application template, which is
contained in the SAP Fiori Application Category. The SAPUI5 Application template is
an empty SAPUI5 project skeleton that includes a start view and its corresponding
controller. Click on Next.
Figure 7.7 Creating a New SAPUI5 Application in the SAP Web IDE Full-Stack
270
第 271 页 / 共 465 页
7.2 Application Development with the SAP Web IDE Full-Stack
On the subsequent project creation wizard screen shown in Figure 7.8, provide the
Project Name and the Namespace with which to prefix the SAPUI5 artifacts. Choose
Next.
Figure 7.8 Providing a Project Name and Namespace for the Freestyle SAPUI5 Project
In the template customization step shown in Figure 7.9, specify the View Type and a
View Name for the initial view. The XML view type is chosen because it’s the recom-
mended view type for defining SAPUI5 views. Call the initial view “CreatePurchaseD-
ocument”, and click on Finish.
Figure 7.9 Selecting the View Type and Defining a View Name for the Initial View
To set up the connection to the backend OData service developed in Chapter 4, open the
app descriptor manifest.json file. The app descriptor is the central point containing
271
第 272 页 / 共 465 页
7 Developing a Freestyle Application
all application-relevant configurations, including the app’s data sources, models, and
routing settings. To enable the OData service, including its UI annotation model, for
usage within the application, add it to the data sources of the application (see Figure
7.10). In general, there are two ways to edit the app descriptor: the form-based
Descriptor Editor or the JavaScript Object Notation (JSON)-based code editor. Use the
descriptor editor, and click on the OData Services Add (+) button.
Figure 7.10 Descriptor Editor to Edit the App Descriptor manifest.json File’s Data Sources
A dialog wizard, shown in Figure 7.11, appears to configure the Data Connection. First
enter the SAP Gateway hub system the service has been activated on. Then you can
choose the service by searching for it using the Services search field. When you’ve
found and selected the service, click on Next.
In the Model Selection step shown in Figure 7.12, you provide a model name for the
previously defined OData service connection or set it as the default model of the
application. We want to set the data connection as the default model because it’s the
primary OData service of the application and therefore its default data source. Click
on Next and then on Finish.
272
第 273 页 / 共 465 页
7.2 Application Development with the SAP Web IDE Full-Stack
Figure 7.11 Selecting the OData Service to Add as a Data Source to the App Descriptor
Figure 7.12 Setting the Previously Created Data Connection as the Default Model of the App
273
第 274 页 / 共 465 页
7 Developing a Freestyle Application
Now that the OData service has been chosen and assigned to the default model, you
need to configure the annotation source to be able to use smart controls (Figure 7.13).
Click the Add New Annotation (+) button in the Annotations part of the screen below
the OData Services part. Call the annotation data source “Z_C_PURCHASEDOCU-
MENTLRP_CDS_VAN” like the actual OData annotation model in the backend, and
set its URI to “/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Annotations(Techni-
calName='Z_C_PURCHASEDOCUMENTLRP_CDS_VAN',Version='0001')/$value/” be-
cause the annotations must be downloaded via the Catalog Service of the SAP
Gateway hub system.
Figure 7.13 Added OData Service and Corresponding Annotation File
To write data back to the OData model and service, you need to set the binding mode
of the model to two-way. Select the Models tab of the descriptor editor, and choose
TwoWay from the Default Binding Mode dropdown (see Figure 7.14).
Figure 7.15 and Figure 7.16 summarize the changes made so far. Figure 7.15 shows the
data sources of the application in the JSON code editor of the manifest.json app
descriptor file. We’ve added the Z_PURCHASEDOCUMENTLRP_CDS OData and Z_C_PURCHASE-
DOCUMENTLRP_CDS_VAN annotations services as data sources to the app. The OData ser-
vice references its UI annotations via the annotations property that points to the
annotation service.
274
第 275 页 / 共 465 页
7.2 Application Development with the SAP Web IDE Full-Stack
Figure 7.14 Setting the Default Model’s Binding Mode to TwoWay
Figure 7.15 OData and OData Annotation Data Sources of the App in the App Descriptor
Code Editor (manifest.json)
Figure 7.16 shows the models of the applications. The default model of the app refer-
ences the Z_PURCHASEDOCUMENTLRP_CDS OData service and configures the OData service
using additional properties, for instance, the binding and count mode. The model
that will be instantiated by the UI component file (Component.js) on app startup is a
sap.ui.model.odata.v2.ODataModel model. The i18n model provides language-depen-
dent texts stored locally in i18n-*.properties files, for instance, i18n-en.properties for
English. Texts stored as properties in i18n-*.properties files can be accessed in XML
views via the i18n model using relative bindings ({i18n>PROPERTY}). For simplicity,
275
第 276 页 / 共 465 页
7 Developing a Freestyle Application
we’ll hard-code the UI texts and not fetch them from language dependent i18n-*.prop-
erties files.
Figure 7.16 Default OData and i18n Models of the App in the App Descriptor Code Editor
(manifest.json)
7.2.2 Object Creation Page Using Smart Fields and Forms
The app is now connected to an OData model, and you can start with the actual appli-
cation development. Remember, when we created the project, SAP Web IDE already
created a view for us that we called CreatePurchaseDocument.view.xml. As the next
step, we want to insert a smart form for creating purchase documents into the cur-
rently empty view definition that contains several smart fields for entering the initial
data of a purchase document. Additionally, we’ll add a save button to the footer of the
page to trigger the submission of the purchase document to the backend OData ser-
vice. The final smart form definition is shown in Figure 7.17. You need to set the edit-
able attribute of the SmartForm to true to render sap.m.Input fields instead of sap.m.
Text fields for the different SmartFields. You’ll also set the checkButton attribute to
true to be able to evaluate the input data against the OData metadata of the respec-
tive bound OData entity type property. Inside the SmartForm, you only define one
Group without a label and add several GroupElement controls, including SmartFields
to its default groupElements aggregation. More specifically, you add SmartFields fields
for the PurchaseDocument ID, Description, Priority, PurchasingOrganization, and Pur-
chaseDocumentImageURL properties of the Z_C_PurchaseDocumentLRP OData entity and
bind their values to the respective value attribute of the SmartField control.
276
第 277 页 / 共 465 页
7.2 Application Development with the SAP Web IDE Full-Stack
Figure 7.17 Smart Form Definition for Creating a Purchase Document
However, to render the UI based on OData metadata and UI annotations, the Smart-
Form and its child controls require bindings to OData entity properties to obtain their
metadata information. The binding to a newly created purchase document entity can
be established via the respective view controller. When the initial CreatePurchaseD-
ocument.view.xml file was created by SAP Web IDE, a corresponding CreatePurchase-
Document.controller.js file was also generated to serve as the controller of this view.
As we’ve added the OData service as the default model to the app descriptor, a sap.ui.
model.odata.v2.ODataModel will be instantiated automatically by the SAPUI5 frame-
work. Automatically instantiated models can be retrieved in controllers by calling
this.getView().getModel(). Having to retrieve a model from a controller happens
quite frequently, so it makes sense to move this functionality to a BaseController.js
module from which other view controllers inherit (Listing 7.8). We can then get the
OData model inside view controllers by simply calling this.getModel().
277
第 278 页 / 共 465 页
7 Developing a Freestyle Application
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function (Controller) {
"use strict";
return Controller.extend("…/BaseController", {
…
getModel: function (sName) {
var oModel = this.getView().getModel(sName);
if (!oModel) {
oModel = this.getOwnerComponent().getModel(sName);
}
return oModel;
}
…
)};
)};
Listing 7.8 BaseController.js Module Containing a Convenience getModel Method for
Retrieving the View’s Model inside the View Controller
Creating a new empty purchase document in the controller and binding it to the view
is shown in Listing 7.9. In the onInit method of the CreatePurchaseDocument control-
ler, you attach to the route-matched event, which is fired every time the CreatePur-
chaseDocument route is displayed (more specifically, the URL hash matches the
pattern defined for the route). When the event is fired, the _createPurchaseDocument
callback function is called. Inside the function, you wait until the metdataLoaded
promise is resolved, and if the model has no pending changes, you create a new pur-
chase document entity by calling the createEntry method of the OData model with
the entity set name Z_C_PurchaseDocumentLrp as a parameter. The new purchase doc-
ument isn’t immediately sent to the backend, but instead a request is stored in a
request queue.
sap.ui.define([
"…/BaseController"
], function (BaseController) {
"use strict";
return BaseController.extend("…/CreatePurchaseDocument",
{
onInit: function () {
this.getRouter().getRoute("CreatePurchaseDocument").
attachMatched(this._createPurchaseDocument, this);
278
第 279 页 / 共 465 页
7.2 Application Development with the SAP Web IDE Full-Stack
},
_createPurchaseDocument: function () {
this.getModel().metadataLoaded().then(function () {
if (!this.getModel().hasPendingChanges()) {
var oPurchaseDocumentBindingContext =
this.getModel().createEntry("/Z_C_PurchaseDocumentLrp");
this.getView().bindElement(oPurchaseDocumentBindingContext.getPath());
}
}.bind(this));
}
});
});
Listing 7.9 Create a New Purchase Document Entry and Bind It to the View
To run the app from the Files pane in the SAP Web IDE full-stack, simply select the root
folder of the app, and choose Run (cid:2) Run As (cid:2) Web Application from its context menu. At
runtime, the Create Purchase Document view will look as shown in Figure 7.18.
Figure 7.18 Create Purchase Document View at Runtime
279
第 280 页 / 共 465 页
7 Developing a Freestyle Application
To finally send the newly created purchase document entity to the backend when the
Save button is clicked and its onSave event handler function is triggered inside the
controller, we must call the submitChanges method of the sap.ui.model.odata.v2.
ODataModel instance as shown in Listing 7.10. Its parameter object contains a success
callback function that is called on success and an error callback function that is called
on error. If submitting the changes to the backend was successful, you also unbind
the entity from the view so that you can create and bind a new entity.
sap.ui.define([
"…BaseController"
], function (BaseController) {
"use strict";
return BaseController.extend("…CreatePurchaseDocument",
{
onSave: function () {
…
this.getModel().submitChanges({
success: function () {
this.getView().unbindElement();
}.bind(this),
error: function () {…}.bind(this)
});
…
}
});
});
Listing 7.10 Submit the Collected Changes to the Backend in the onSave Method of
the CreatePurchaseDocument.controller.js Module
Before you submit the new purchase document to the backend, you should first eval-
uate its values against the metadata of the service to detect any obvious client errors
by calling the smart form’s check method. As shown in Listing 7.11, we must first fetch
the smart form instance by its ID and then call its check method. The check method
then returns an array of fields having errors in relation to the fields’ OData metadata,
for instance, the field is too long (MaxLength) or is an empty mandatory field (Nul-
lable="false").
280
第 281 页 / 共 465 页
7.2 Application Development with the SAP Web IDE Full-Stack
onSave: function () {
var oSmartForm = this.getView().byId("createPurchaseDocumentSmartForm");
var aSmartFieldsWithErrors = oSmartForm.check();
if (aSmartFieldsWithErrors.length !== 0) {
return;
}
…
}
Listing 7.11 Call the Smart Form’s Check Method to Check for Client Errors before
Submitting Changes to the Backend
On submitting a newly created purchase document to the backend, you need to nav-
igate to a new view that will display all purchase documents created so far. To display
and filter the purchase documents, you’ll use a smart table and smart filter bar con-
trol. For the moment, let’s create a new empty view called DisplayPurchaseDocuments.
view.xml. You first want to set up the navigation between the CreatePurchaseDocu-
ment.view.xml and the DisplayPurchaseDocuments.view.xml view before implement-
ing the view definition. Additionally, you’ll create another view called App.view.xml
that will serve as the root view of the application (see Listing 7.12). Inside the view,
you’ll only put a single sap.m.App control with the ID app as the root container for all
the views. Other views will then be dynamically inserted into the pages aggregation of
the instantiated sap.m.App control at runtime. The sap.m.App control inherits its nav-
igation capabilities from the sap.m.NavContainer and additionally adds some header
tags to the HTML page that are considered useful for mobile applications.
<mvc:View xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc" displayBlock="true" >
<App id="app" busy="{app>/isBusy}" busyIndicatorDelay="0"/>
</mvc:View>
Listing 7.12 Root View of the Application
The navigation routes and targets of the application are defined in the sap.ui5 name-
space of the app descriptor manifest.json file. However, the first thing you must do
now is to make the newly created App.view.xml the root view of the application. Fig-
ure 7.19 shows how the App.view.xml can be defined as the root view of the applica-
tion inside the manifest.json app descriptor file.
281
第 282 页 / 共 465 页
7 Developing a Freestyle Application
Figure 7.19 Defining the Root View of the Application in the App Descriptor manifest.json File
The navigation configuration of the application is shown in Figure 7.20. In the config
object of the routing configuration, you should define some default configuration
relevant for routes and targets, for instance, the viewType or transition. The controlId
and controllAggregation properties are relevant for sap.m.routing.Targets and
define that new views accessed using navigation will be inserted into the pages aggre-
gation of the sap.m.App container control inside the root view (App.view.xml) with the
ID app. The CreatePurchaseDocument route is mapped to the CreatePurchaseDocument
view via the CreatePurchaseDocument target. Its pattern is empty, as it’s the start
page of the application. The DisplayPurchaseDocuments route is mapped to the Dis-
playPurchaseDocuments view via the DisplayPurchaseDocuments target.
Figure 7.20 Mapping from Routes to Target Views in the App Descriptor manifest.json File
282
第 283 页 / 共 465 页
7.2 Application Development with the SAP Web IDE Full-Stack
You can directly navigate to the DisplayPurchaseDocuments view by appending the
fragment identifier #/DisplayPurchaseDocuments to the application URL as its route
pattern is DisplayPurchaseDocuments.
To programmatically navigate in controllers from one view to another, you first have
to retrieve an instance of the sap.m.routing.Router defined in the app descriptor
manifest.json file. How you can do this inside a controller is shown in Listing 7.13. We
define a new convenience function in the base controller from which all other con-
trollers inherit as this is a functionality that most controllers will need.
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function (Controller) {
"use strict";
return Controller.extend("…/BaseController", {
…
getRouter: function () {
return sap.ui.core.UIComponent.getRouterFor(this);
}
…
)};
)};
Listing 7.13 Function Definition for Obtaining an Automatically Instantiated sap.m.routing.
Router Instance via the sap.ui.core.UIComponent
If you then want to navigate from the create view to the display view, after the new
purchase document has been submitted to the backend, you simply execute the code
shown in Listing 7.14. The route name of the view you want to navigate to (Display-
PurchaseDocuments) is simply passed as a parameter to the navTo function of the sap.
m.routing.Router object.
this.getRouter().navTo("DisplayPurchaseDocuments");
Listing 7.14 Navigate from the Create View to the Display View
This navigation is triggered inside the success callback of the submitChanges call
shown earlier in Listing 7.10.
7.2.3 List Report Page Using Smart Table and Filter Bar
So far, we haven’t defined the content of the DisplayPurchaseDocuments.view.xml but
we’ll do that now. As already mentioned, we want to display all available purchase
283
第 284 页 / 共 465 页
7 Developing a Freestyle Application
documents in a table and enable filtering. Hence, we’ll use a smart table to display the
purchase documents and a smart filter bar to filter the table data. Figure 7.21 shows
the view definition containing a smart filter bar and smart table. Both controls are
bound to the Z_C_PurhaseDocumentLrp entity set, and the smart table and filter bar are
connected by the smart filter bar ID purchaseDocumentsSmartFilterBar.
Figure 7.21 Definition of the DisplayPurchaseDocuments.view.xml View Containing a
Smart Table and Filter Bar Control
If you run the app now and create a purchase document, you should automatically be
forwarded to the Display Purchase Documents view, which will display all available
purchase documents in a smart table (see Figure 7.22). To create purchase document
items, you could simply navigate to the created object page (from Chapter 4) by click-
ing on the Purchase Documents Manage link. From there, you could add items to the
purchase document.
Figure 7.22 Display Purchase Documents View at Runtime
284
第 285 页 / 共 465 页
7.2 Application Development with the SAP Web IDE Full-Stack
7.2.4 Add an OPA5 Integration Test
To test and safeguard the creation and display of a purchase document with the
application, including the navigation between the create and display views, you
should write a UI integration test. You can do that with the help of the One-Page
Acceptance test (OPA5) (for SAPUI5) framework, which is part of the SAPUI5 SDK. It
provides an API for SAPUI5 controls and eases testing user interactions and naviga-
tions by hiding asynchronicity. OPA5 can be used with any JavaScript unit test
framework, but, in our case, we’ll use QUnit as the basic test runner and unit test
framework. You can easily set up a default test structure for a SAPUI5 application
project in the SAP Web IDE full-stack by selecting the project root folder and choosing
New (cid:2) Test Structure (Figure 7.23).
Figure 7.23 Creating a Test Structure for a SAPUI5 Application Project in the SAP Web IDE
Full-Stack
This will generate a test folder with unit and integration test subfolders. The unit sub-
folder will contain QUnit test skeletons, and the integration subfolder will contain
OPA5 integration test skeletons. The generated structure is shown in Figure 7.24. We’ll
285
第 286 页 / 共 465 页
7 Developing a Freestyle Application
ignore unit testing for now and focus on the UI integration testing part in the follow-
ing. The SAP Web IDE has also generated a default OPA5 journey (navigationJourney.
js), which we’ll also ignore and delete because we’ll create our own journey. Never-
theless, getting this test structure generated is quite convenient; for instance, we
now already have a test runner (opaTests.qunit.html) for running our OPA5 journeys
to be developed and a module collecting all the journeys (AllJourneys.js). We’ll later
adapt the files contained in the pages folder to our needs. OPA5 tests are organized in
journeys and pages. A journey contains all test cases for a specific use case and struc-
tures its test cases according to high-level given-when-then sentences hiding the
actual test case implementation. Journeys use pages that implement the necessary
arrangements, actions, and assertions used within the high-level journey definition
and are usually related to a single view.
Figure 7.24 Generated Test Structure for the SAPUI5 Application Project
Setting Up a Mock Server
Because you don’t want to depend on any backend OData service or data being avail-
able when testing the purchase document creation, navigation and display process in
the UI, you should use a mock server. The mock server will intercept the OData
requests the application sends to the backend and return local mock data instead. It
supports create, read, update, and delete (CRUD) requests and stores changes locally
during one session. To create a mock server, you create a mockserver.js module in the
localService folder of the project where you’ll set up and configure the server. Listing
7.15 shows how you instantiate the mock server with the root URI of the OData service
286
第 287 页 / 共 465 页
7.2 Application Development with the SAP Web IDE Full-Stack
and simulate the service by specifying the local service metadata document (meta-
data.xml) and the base folder for mock data (mockdata). By setting the bGener-
ateMissingMockData property to true, the mock server will automatically generate
mock data for entities that don’t have dedicated mock data in the mockdata folder.
sap.ui.define([
"sap/ui/core/util/MockServer"
], function (MockServer) {
"use strict";
return {
init: function () {
var oMockServer = new MockServer({
rootUri: "/sap/opu/odata/sap/Z_C_PURCHASEDOCUMENTLRP_CDS/"
});
oMockServer.simulate("../localService/metadata.xml", {
sMockdataBaseUrl: "../localService/mockdata",
bGenerateMissingMockData: true
});
oMockServer.start();
}
};
});
Listing 7.15 Instantiate the sap.ui.core.util.MockServer with the OData Service’s Root URI
and Simulate the OData Service by Specifying the Metadata XML File and the Base Folder of
the Mock Data
Create Mock Data
You can easily create proper mock data for the entities by right-clicking on the local
metadata.xml file in the localService folder and choosing Edit Mock Data. This will
bring up the Edit Mock Data dialog shown in Figure 7.25. You can then select entities
in the Entity Sets pane and create mock data by clicking on the Add Row button in the
upper-right corner.
287
第 288 页 / 共 465 页
7 Developing a Freestyle Application
Figure 7.25 Creating Mock Data for the OData Service Defined in the Local Service Metadata
Document (metadata.xml)
When you click OK, mock data JSON files are created in the mockdata folder contain-
ing the previously defined data (see Figure 7.26).
Figure 7.26 Created Mock Data JSON Files
Because you’re using smart controls, the application also heavily depends on the UI
annotation model for rendering the controls. If you want to completely isolate the
application from the backend, you also must mock the Catalog Service, which is pro-
viding the UI annotation model of the OData service, as shown in Listing 7.16. In case
288
第 289 页 / 共 465 页
7.2 Application Development with the SAP Web IDE Full-Stack
of the UI annotation mock server, you define an additional request, matching all GET
requests sent to the service that will simply return the local annotation file Z_C_PUR-
CHASEDOCUMENTLRP_CDS_VAN.xml.
…
var oAnnotationMockServer = new MockServer({
rootUri: "/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Annotations(TechnicalName=
'Z_C_PURCHASEDOCUMENTLRP_CDS_VAN',Version='0001')/$value/"
});
oAnnotationMockServer.setRequests([{
method: "GET",
path: new RegExp(".*"),
response: function (oXhr) {
var oLocalAnnotations = jQuery.sap.sjax({
url: sAnnotationURL,
datatype: "XML"
}).data;
oXhr.respondXML(200, {}, jQuery.sap.serializeXML
(../localService/Z_C_PURCHASEDOCUMENTLRP_CDS_VAN.xml));
return true;
}
}]);
oAnnotationMockServer.start();
…
Listing 7.16 Create Local Mock Server for the UI Annotations
Note
To update the local metadata file in the SAP Web IDE full-stack, you can simply right-
click on the file and choose Sync Metadata.
Creating an OPA5 UI Integration Test
The journey we want to implement and test is shown in Listing 7.17 and should
almost read like a manual test case description or user story. This is called behavior-
driven development (BDD). The general idea of BDD is that requirements are defined
289
| Note To update the local metadata file in the SAP Web IDE full-stack, you can simply right- |
| click on the file and choose Sync Metadata. |
第 290 页 / 共 465 页
7 Developing a Freestyle Application
in the form of high-level given-when-then sentences and later executed as auto-
mated tests. This eases the definition of requirements and test scenarios by nontech-
nical stakeholders. With this journey, we want to test the creation, saving, and correct
display in the smart table of the purchase document after the automatic navigation
to the display view took place.
sap.ui.define([
"sap/ui/test/opaQunit"
], function (opaTest) {
"use strict";
QUnit.module("Create Purchase Document Journey");
opaTest("Should see the newly created Purchase Document in the Smart Table",
function (Given, When, Then) {
Given.iStartTheApp();
var oPurchaseDocument = {
PurchaseDocument: "1",
Description: "OPA5 Test Description",
Priority: "1",
PurchasingOrganization: "ORG1",
PurchaseDocumentImageURL: "../images/book.jpg"
};
When.onTheCreatePurchaseDocumentPage
.iEnterPurchaseDocumentData(oPurchaseDocument)
.and
.iSavePurchaseDocument();
Then.onTheDisplayPurchaseDocumentsPage
.iCheckPurchaseDocumentIsInTable(oPurchaseDocument)
.and.iTeardownMyUIComponent();
});
});
Listing 7.17 An OPA5 Journey for Creating and Displaying a Purchase Document
(createPurchaseDocumentJourney.js)
290
第 291 页 / 共 465 页
7.2 Application Development with the SAP Web IDE Full-Stack
The Given object provided as a parameter to the opaTest function contains the
arrangements of all loaded pages, the When object in turn contains all actions, and the
Then object contains all assertions. In our journey, we’re calling one arrangement
(iStartTheApp) method, which will start the app’s UI component (Component.js) and
the mock servers defined in the mockserver.js module by calling its init method. The
arrangement is implemented in the Common.js module, which serves as the base class
of all page objects we’re going to implement (Figure 7.27).
Figure 7.27 Common.js Base Object for OPA5 Pages Containing the iStartApp Arrangement
The iEnterPurchaseDocumentData and iSavePurchaseDocument actions are implemented
in the CreatePurchaseDocument.js page. Figure 7.28 shows a part of the implementa-
tion of the iEnterPurchaseDocumentData action. We’re using sap.ui.test.Opa5’s
waitFor method to wait until the control we want to select using its view and Smart
Field ID has been rendered. As soon as the control is selected, we’re calling the pre-
defined sap.ui.test.actions.EnterText action to enter text into the input fields. We
fill out the purchase document creation form like this.
After the form has been filled out, we’ll call the iSavePurchaseDocument action from
the createPurchaseDocumentJourney.js journey. In this action, shown in Listing 7.18,
we’ll simply press the Save button using the predefined sap.ui.test.actions.Press
action as soon as the button has been found by its ID inside the CreatePurchaseDocu-
ment view.
iSavePurchaseDocument: function () {
// Press Save Button
291
第 292 页 / 共 465 页
7 Developing a Freestyle Application
return this.waitFor({
viewName: sCreatePurchaseDocumentView,
id: "savePurchaseDocumentButton",
actions: new Press()
});
}
Listing 7.18 iSavePurchaseDocument Action also Implemented in the CreatePurchase-
Document.js OPA5 Page Object
Figure 7.28 The CreatePurchaseDocument.js OPA5 Page Object Containing the
iEnterPurchaseDocumentData Action
292
第 293 页 / 共 465 页
7.2 Application Development with the SAP Web IDE Full-Stack
For the iCheckPurchaseDocumentIsInTable assertion, checking the presence of the
newly created purchase document in the smart table, we create a new page called
DisplayPurchaseDocuments.js as the assertion logically belongs to the DisplayPur-
chaseDocuments.view.xml. In the assertion shown in Figure 7.29, the smart table is first
selected by its view and ID. On top of that, two custom matchers are implemented to
find the newly created purchase document inside the smart table.
Figure 7.29 Assertion (iCheckPurchaseDocumentIsInTable) Checks the Display of the Newly
Created Purchase Document in the Smart Table on the DisplayPurchaseDocuments View
293
第 294 页 / 共 465 页
7 Developing a Freestyle Application
The first matcher function extracts the table contained inside the smart table; in our
case, this will be a sap.m.Table. The second matcher function fetches the items of the
table and iterates over them using the JavaScript some function, which checks
whether one element in an array fulfills a certain condition. We’ll return true in the
callback function if one purchase document table item is equal to the expected pur-
chase document. We check the equality of the actual and expected purchase docu-
ment using a naïve comparison function (purchaseDocumentsAreEqual) that simply
compares the purchase document properties used for creation of two purchase doc-
ument objects. If the second matcher returns true, the success function will be called,
and the test is executed successfully. If a time out happens because the purchase doc-
ument isn’t found, the errorMessage property will be displayed, and the test will fail.
When we created the test structure for our project, the SAP Web IDE full-stack had
already created an AllJourneys.js module file and an opaTests.qunit.html file for load-
ing and executing the OPA5 integration tests. To run the new integration tests, we
must load our journey (createPurchaseDocumentJourney) and the corresponding pages
(CreatePurchaseDocument and DisplayPurchaseDocuments) inside the AllJourneys.js
module file shown in Figure 7.30. Using the opaTests.qunit.html file, we can then
directly execute the journey because it’s already loading the AllJourneys.js module
file. We can run the tests by selecting the opaTests.qunit.html file, right-clicking, and
choosing Run (cid:2) Run As (cid:2) Web Application.
Figure 7.30 AllJourneys.js Module: Central module for Loading all OPA5 Journeys and Pages
Needed for Testing the App
294