ABAP Development for SAP S4HANA (SAP PRESS) (第10页)

第 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

返回ABAP参考资料列表