Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 

This is the second part of a tutorial series about how to build your own SAP Fiori Approve Purchase Order app.


 


The purpose of this tutorial is to show you step-by-step how to build your own SAP Fiori Approve Purchase Orders app and provides additional insights into why certain aspects have been developed as they are.


 

Please see the introductory post (Tutorial: Build Your Own SAP Fiori Approve Purchase Order App) for background information regarding this tutorial.

 

In this second part, we're going to set up the app using the SAP Web IDE and run the app using mock data.

 

Set up the app

 

We’ll set up our app using the master-detail template that we’ll configure to show purchase orders (based on a mock service definition). The master-detail template already contains a lot of generic functionality that can be easily extended with additional coding. We’ll do this later on in this tutorial.

 

 

Create a new project in the SAP Web IDE

 

  1. Launch the SAP Web IDE

  2. From start screen, select New Project from Template or File --> New --> Project from Template

  3. Select SAP Fiori Master-Detail Application.

  4. If required, change the SAPUI5 version.

  5. Start the configuration wizard of this template by clicking Next.


 

Add basic information

 

On the Basic Information screen, enter a name for your project. We use acme.purchaseorder because this is the same name that is used later for our application.

 

Add metadata using a file system

 

Fiori apps use OData services to send and retrieve data from a backend. As rule of thumb, one SAP Fiori app uses one OData service. The wizard uses the metadata of this service ($metadata) to configure our application. Since we haven’t created an OData service yet, we need to specify the required metadata based on a preliminary service. We have a preliminary service here for you to use.

 


Good to know


One app = one dedicated OData service


 

Each OData service is based on and described by its individual entity-relationship model. This model is tailored to the data needs of one SAP Fiori application so that the service only allows access to the data needed by the application.

Although it may seem like a restriction, one dedicated OData service simplifies the life cycle of an SAP Fiori application and its data access service in several ways:

  • only one (additional) service needs to be granted to a user in order to use the application

  • only one service needs to be activated to operate the application

  • only one service needs to be upgraded in sync with the application

  • only one application needs to be upgraded in sync with the service


 

Note that this rule applies to the programming model level only: app-specific, hand-crafted code and app-specific hand-crafted data binding should depend only on one app-specific service publishing all app-specific data.

This rule is not a technical restriction: an app must be able to use several OData services indirectly via reusable app parts, or via dynamic code or data binding that is at run-time derived from metadata. Examples are (and are not limited to):

  • SAP Fiori applications also rely on the central services e.g. exposed by the shell.

  • Message long texts are provided by one central reuse service, indirectly accessed via messaging APIs or UI controls.


Create metadata

We're now going to build the metadata.xml document to use in this tutorial:

1. Open an editor of your choice, for example, Notepad++, to put together the metadata document.

2. Create a new document and add the following code. The code is the XML declaration that defines the metadata document as an XML document.





<?xml version="1.0" encoding="utf-8"?>

3. Next, add the entity data model XML (edmx) after the XML declaration. The XML declaration envelopes the schema, entity types, and further content.
<edmx:Edmx Version="1.0"
xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns:sap="http://www.sap.com/Protocols/SAPData">
<edmx:Reference Uri="./IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_COMMON',Version='0001',SAP__Origin='LOCAL')/$value"
xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
<edmx:Include Namespace="com.sap.vocabularies.Common.v1" Alias="Common"/>
</edmx:Reference>
<edmx:DataServices m:DataServiceVersion="2.0">
...
</edmx:DataServices>
</edmx:Edmx>

4. Once you’ve added the edmx code, you need to add the schema. The schema acts like a container for the content. Add the following code between the edmx:DataServices tags.
<Schema Namespace="Z_PO_TUTORIAL_SRV" xml:lang="de" sap:schema-version="1"
xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
...
</Schema>
</edmx:DataServices>

5. Entity types define the data model for the Odata collection. In this tutorial, there are two entity types: purchase orders and purchase order items. Add the following code between the schema tags.
<EntityType Name="Z_C_PurchaseorderType" sap:content-version="1">
<Key>
<PropertyRef Name="POId"/>
</Key>
<Property Name="POId" Type="Edm.String" Nullable="false" MaxLength="10" sap:display-format="UpperCase" sap:label="Bestell-ID" sap:creatable="false" sap:updatable="false"/>
<Property Name="DeliveryAddress" Type="Edm.String" MaxLength="129" sap:display-format="UpperCase" sap:label="Delivered To"/>
<Property Name="GrossAmount" Type="Edm.Decimal" Precision="16" Scale="3" sap:unit="CurrencyCode" sap:label="Gross Amount"/>
<Property Name="CurrencyCode" Type="Edm.String" MaxLength="5" sap:label="Währungscode" sap:semantics="currency-code"/>
<Property Name="ItemCount" Type="Edm.Int32"/>
<Property Name="SupplierName" Type="Edm.String" MaxLength="80" sap:label="Firma"/>
<Property Name="OrderedByName" Type="Edm.String" MaxLength="81" sap:display-format="UpperCase" sap:label="Ordered By"/>
<Property Name="DeliveryDateEarliest" Type="Edm.DateTimeOffset" Precision="7" sap:label="Delivery Date"/>
<Property Name="LaterDelivDateExist" Type="Edm.Int32"/>
<Property Name="ChangedAt" Type="Edm.DateTimeOffset" Precision="7" sap:label="Changed at" sap:creatable="false" sap:updatable="false"/>
<NavigationProperty Name="to_PurchaseOrderItems" Relationship="Z_PO_TUTORIAL_SRV.assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2" FromRole="FromRole_assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2" ToRole="ToRole_assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2"/>
</EntityType>
<EntityType Name="Z_C_PurchaseorderitemType" sap:content-version="1">
<Key>
<PropertyRef Name="POId"/>
<PropertyRef Name="POItemPos"/>
</Key>
<Property Name="POId" Type="Edm.String" Nullable="false" MaxLength="10" sap:display-format="UpperCase" sap:label="Bestell-ID" sap:creatable="false" sap:updatable="false"/>
<Property Name="POItemPos" Type="Edm.String" Nullable="false" MaxLength="10" sap:display-format="UpperCase" sap:label="Zeile der Position"/>
<Property Name="GrossAmount" Type="Edm.Decimal" Precision="16" Scale="3" sap:unit="GrossAmountCurrency" sap:label="Gross Amount"/>
<Property Name="GrossAmountCurrency" Type="Edm.String" MaxLength="5" sap:label="Währungscode" sap:semantics="currency-code"/>
<Property Name="Quantity" Type="Edm.Decimal" Precision="13" Scale="3" sap:unit="QuantityUnit" sap:label="Quantity"/>
<Property Name="QuantityUnit" Type="Edm.String" MaxLength="3" sap:label="Maßeinheit" sap:semantics="unit-of-measure"/>
<Property Name="DeliveryDate" Type="Edm.DateTimeOffset" Precision="7" sap:label="Delivery Time"/>
<Property Name="Price" Type="Edm.Decimal" Precision="16" Scale="3" sap:unit="PriceCurrency" sap:label="Price"/>
<Property Name="PriceCurrency" Type="Edm.String" MaxLength="5" sap:label="Währungscode" sap:semantics="currency-code"/>
<Property Name="Product" Type="Edm.String" MaxLength="255" sap:display-format="UpperCase" sap:label="Product Name"/>
</EntityType>

6. Next, add the following code that defines the complex type and associations. Add the following code after the last EntityType tag.
<ComplexType Name="FunctionImportResult">
<Property Name="Success" Type="Edm.Boolean" Nullable="false" sap:label="boolsche Variable (X=true, -=false, space=unknown)" sap:creatable="false" sap:updatable="false" sap:sortable="false" sap:filterable="false"/>
</ComplexType>
<Association Name="assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2" sap:content-version="1">
<End Type="Z_PO_TUTORIAL_SRV.Z_C_PurchaseorderType" Multiplicity="1" Role="FromRole_assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2"/>
<End Type="Z_PO_TUTORIAL_SRV.Z_C_PurchaseorderitemType" Multiplicity="*" Role="ToRole_assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2"/>
<ReferentialConstraint>
<Principal Role="FromRole_assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2">
<PropertyRef Name="POId"/>
</Principal>
<Dependent Role="ToRole_assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2">
<PropertyRef Name="POId"/>
</Dependent>
</ReferentialConstraint>
</Association>

7. To expose the Odata collection, we need to add an entity container. Add the following code after the last Association tag.
 <EntityContainer Name="Z_PO_TUTORIAL_SRV_Entities" m:IsDefaultEntityContainer="true" sap:supported-formats="atom json xlsx">
<EntitySet Name="Z_C_Purchaseorder" EntityType="Z_PO_TUTORIAL_SRV.Z_C_PurchaseorderType" sap:creatable="false" sap:updatable="false" sap:deletable="false" sap:content-version="1"/>
<EntitySet Name="Z_C_Purchaseorderitem" EntityType="Z_PO_TUTORIAL_SRV.Z_C_PurchaseorderitemType" sap:creatable="false" sap:updatable="false" sap:deletable="false" sap:content-version="1"/>
<AssociationSet Name="assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2" Association="Z_PO_TUTORIAL_SRV.assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2" sap:creatable="false" sap:updatable="false" sap:deletable="false" sap:content-version="1">
<End EntitySet="Z_C_Purchaseorder" Role="FromRole_assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2"/>
<End EntitySet="Z_C_Purchaseorderitem" Role="ToRole_assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2"/>
</AssociationSet>
<FunctionImport Name="ApprovePurchaseOrder" ReturnType="Z_PO_TUTORIAL_SRV.FunctionImportResult" m:HttpMethod="POST">
<Parameter Name="Note" Type="Edm.String" Mode="In" MaxLength="255"/>
<Parameter Name="POId" Type="Edm.String" Mode="In" MaxLength="10"/>
</FunctionImport>
<FunctionImport Name="RejectPurchaseOrder" ReturnType="Z_PO_TUTORIAL_SRV.FunctionImportResult" m:HttpMethod="POST"/>
</EntityContainer>

8. Finally, add the link relations after the last EntityContainer tag.
<atom:link rel="self" href="./sap/Z_PO_TUTORIAL_SRV/$metadata"
xmlns:atom="http://www.w3.org/2005/Atom"/>
<atom:link rel="latest-version" href="./sap/Z_PO_TUTORIAL_SRV/$metadata"
xmlns:atom="http://www.w3.org/2005/Atom"/>

Once you're done putting together the metadata file, it should like something like this:
<?xml version="1.0" encoding="utf-8"?/>
<edmx:Edmx Version="1.0"
xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns:sap="http://www.sap.com/Protocols/SAPData"/>
<edmx:Reference Uri="./IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_COMMON',Version='0001',SAP__Origin='LOCAL')/$value"
xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"/>
<edmx:Include Namespace="com.sap.vocabularies.Common.v1" Alias="Common"/>
</edmx:Reference/>
<edmx:DataServices m:DataServiceVersion="2.0"/>
<Schema Namespace="Z_PO_TUTORIAL_SRV" xml:lang="de" sap:schema-version="1"
xmlns="http://schemas.microsoft.com/ado/2008/09/edm"/>
<EntityType Name="Z_C_PurchaseorderType" sap:content-version="1"/>
<Key/>
<PropertyRef Name="POId"/>
</Key/>
<Property Name="POId" Type="Edm.String" Nullable="false" MaxLength="10" sap:display-format="UpperCase" sap:label="Bestell-ID" sap:creatable="false" sap:updatable="false"/>
<Property Name="DeliveryAddress" Type="Edm.String" MaxLength="129" sap:display-format="UpperCase" sap:label="Delivered To"/>
<Property Name="GrossAmount" Type="Edm.Decimal" Precision="16" Scale="3" sap:unit="CurrencyCode" sap:label="Gross Amount"/>
<Property Name="CurrencyCode" Type="Edm.String" MaxLength="5" sap:label="Währungscode" sap:semantics="currency-code"/>
<Property Name="ItemCount" Type="Edm.Int32"/>
<Property Name="SupplierName" Type="Edm.String" MaxLength="80" sap:label="Firma"/>
<Property Name="OrderedByName" Type="Edm.String" MaxLength="81" sap:display-format="UpperCase" sap:label="Ordered By"/>
<Property Name="DeliveryDateEarliest" Type="Edm.DateTimeOffset" Precision="7" sap:label="Delivery Date"/>
<Property Name="LaterDelivDateExist" Type="Edm.Int32"/>
<Property Name="ChangedAt" Type="Edm.DateTimeOffset" Precision="7" sap:label="Changed at" sap:creatable="false" sap:updatable="false"/>
<NavigationProperty Name="to_PurchaseOrderItems" Relationship="Z_PO_TUTORIAL_SRV.assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2" FromRole="FromRole_assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2" ToRole="ToRole_assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2"/>
</EntityType/>
<EntityType Name="Z_C_PurchaseorderitemType" sap:content-version="1"/>
<Key/>
<PropertyRef Name="POId"/>
<PropertyRef Name="POItemPos"/>
</Key/>
<Property Name="POId" Type="Edm.String" Nullable="false" MaxLength="10" sap:display-format="UpperCase" sap:label="Bestell-ID" sap:creatable="false" sap:updatable="false"/>
<Property Name="POItemPos" Type="Edm.String" Nullable="false" MaxLength="10" sap:display-format="UpperCase" sap:label="Zeile der Position"/>
<Property Name="GrossAmount" Type="Edm.Decimal" Precision="16" Scale="3" sap:unit="GrossAmountCurrency" sap:label="Gross Amount"/>
<Property Name="GrossAmountCurrency" Type="Edm.String" MaxLength="5" sap:label="Währungscode" sap:semantics="currency-code"/>
<Property Name="Quantity" Type="Edm.Decimal" Precision="13" Scale="3" sap:unit="QuantityUnit" sap:label="Quantity"/>
<Property Name="QuantityUnit" Type="Edm.String" MaxLength="3" sap:label="Maßeinheit" sap:semantics="unit-of-measure"/>
<Property Name="DeliveryDate" Type="Edm.DateTimeOffset" Precision="7" sap:label="Delivery Time"/>
<Property Name="Price" Type="Edm.Decimal" Precision="16" Scale="3" sap:unit="PriceCurrency" sap:label="Price"/>
<Property Name="PriceCurrency" Type="Edm.String" MaxLength="5" sap:label="Währungscode" sap:semantics="currency-code"/>
<Property Name="Product" Type="Edm.String" MaxLength="255" sap:display-format="UpperCase" sap:label="Product Name"/>
</EntityType/>
<ComplexType Name="FunctionImportResult"/>
<Property Name="Success" Type="Edm.Boolean" Nullable="false" sap:label="boolsche Variable (X=true, -=false, space=unknown)" sap:creatable="false" sap:updatable="false" sap:sortable="false" sap:filterable="false"/>
</ComplexType/>
<Association Name="assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2" sap:content-version="1"/>
<End Type="Z_PO_TUTORIAL_SRV.Z_C_PurchaseorderType" Multiplicity="1" Role="FromRole_assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2"/>
<End Type="Z_PO_TUTORIAL_SRV.Z_C_PurchaseorderitemType" Multiplicity="*" Role="ToRole_assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2"/>

<ReferentialConstraint/>
<Principal Role="FromRole_assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2"/>
<PropertyRef Name="POId"/>
</Principal/>
<Dependent Role="ToRole_assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2"/>
<PropertyRef Name="POId"/>
</Dependent/>
</ReferentialConstraint/>


</Association/>
<EntityContainer Name="Z_PO_TUTORIAL_SRV_Entities" m:IsDefaultEntityContainer="true" sap:supported-formats="atom json xlsx"/>
<EntitySet Name="Z_C_Purchaseorder" EntityType="Z_PO_TUTORIAL_SRV.Z_C_PurchaseorderType" sap:creatable="false" sap:updatable="false" sap:deletable="false" sap:content-version="1"/>
<EntitySet Name="Z_C_Purchaseorderitem" EntityType="Z_PO_TUTORIAL_SRV.Z_C_PurchaseorderitemType" sap:creatable="false" sap:updatable="false" sap:deletable="false" sap:content-version="1"/>
<AssociationSet Name="assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2" Association="Z_PO_TUTORIAL_SRV.assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2" sap:creatable="false" sap:updatable="false" sap:deletable="false" sap:content-version="1"/>
<End EntitySet="Z_C_Purchaseorder" Role="FromRole_assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2"/>
<End EntitySet="Z_C_Purchaseorderitem" Role="ToRole_assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2"/>
</AssociationSet/>
<FunctionImport Name="ApprovePurchaseOrder" ReturnType="Z_PO_TUTORIAL_SRV.FunctionImportResult" m:HttpMethod="POST"/>
<Parameter Name="Note" Type="Edm.String" Mode="In" MaxLength="255"/>
<Parameter Name="POId" Type="Edm.String" Mode="In" MaxLength="10"/>
</FunctionImport/>
<FunctionImport Name="RejectPurchaseOrder" ReturnType="Z_PO_TUTORIAL_SRV.FunctionImportResult" m:HttpMethod="POST"/>
</EntityContainer/>
<atom:link rel="self" href="./sap/Z_PO_TUTORIAL_SRV/$metadata"
xmlns:atom="http://www.w3.org/2005/Atom"/>
<atom:link rel="latest-version" href="./sap/Z_PO_TUTORIAL_SRV/$metadata"
xmlns:atom="http://www.w3.org/2005/Atom"/>
</Schema/>
</edmx:DataServices/>
</edmx:Edmx/>

9. Save the metadata document to your local hard drive.


If you have already created the backend, you can use your existing service definition:

  1. On the Data Connection screen, choose Service Catalog.

  2. Select the system and your OData service.

  3. Click Next to finish this step.


 

If you've imported the service definition from the backend, you need to add a referential constraint to the metadata document. This is necessary to tell the mock server that purchase order and purchase order items are associated via the POId field. Without adding a referential constraint, the mock server would return the entire items collection and the detail would not show the selected item. For more information about referential constraints refer to the mock server FAQ.

 

Open the metadata.xml document located in /webapp/localService. You need to ensure that the yellow roles in the ReferentialConstraint section match with the green Role definition in the code example below.

 

Code example


localService/metadata.xml

 

<Association Name="assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2" sap:content-version="1">

<End Type="Z_PO_TUTORIAL_SRV.Z_C_PurchaseorderType" Multiplicity="1" Role="FromRole_assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2"/>

<End Type="Z_PO_TUTORIAL_SRV.Z_C_PurchaseorderitemType" Multiplicity="*" Role="ToRole_assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2"/>

 

<ReferentialConstraint>

                    <Principal Role="FromRole_assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2">

<PropertyRef Name="POId"/>

</Principal>

                    <Dependent Role="ToRole_assoc_ECD2BAA0F8BF5B8410B95699E8ED23A2">

<PropertyRef Name="POId"/>

</Dependent>

</ReferentialConstraint>

</Association>

 

Customize the template

 

Next, we’ll customize the template based on the metadata we imported in the previous step. Since we imported the metadata, we can just select values from the drop-down lists next to each field.

 

Application Settings































Field Value Description
Type App for SAP Fiori Launchpad Specifies whether we want to build a SAP Fiori App running in Fiori Launchpad or a standalone App.
Title Approve Purchase Orders Title of your application that is displayed in the header area of the window.
Namespace acme.purchaseorder Unique namespace of your application. Since all SAP Fiori apps share the same Javascript VM on the same shared web page, a unique namespace is mandatory.
Description Approve Purchase Orders Short description of your application

 

Data Binding – Object

































Field Value Description
Object Collection Z_C_Purchaseorder

The main entity set that will be displayed in the app. In our case, it’s in the list on the left hand side of the screen.

 

This may vary in case you have created your own OData service with different CDS view names than used in this tutorial. Use the name of the Purchase Order CDS view.
Object Collection ID POId Key that is used to uniquely identify the object collection, in our case the ID of the purchase order.
Object Title SupplierName Name of the item that is displayed in the list
Object Numeric Attribute GrossAmount Number displayed next to the main object
Object Unit of Measure CurrencyCode Unit of measure of the numeric attribute.

 

Data Binding- Line Item




































Field Value Description
Line Item Collection to_PurchaseOrderItems

Entity that will be displayed in the detail section on the right hand side of the screen.

 

This may vary in case you have created your own OData service with different CDS view names than used in this tutorial. Use the Name our Purchase Order Item CDS view.
Line Item Collection ID POItemPos Unique key that is used to identify the object collection.
Line Item Title Product Item title that will be displayed in the table
Line Item Numeric Attribute GrossAmount Numeric attribute of the line item
Line Item Unit of Measure GrossAmountCurrency Unit of measure of the numeric attribute.

 

Click Next and finish to close the wizard.

Overview of project folders and files


A new folder is now available in your local project work space.

 

Let’s briefly check if the app is running. Since we haven’t deployed our app to an SAP Fiori frontend server or configured any backend systems, we need to use the sandbox to start the app. The sandbox provides a runtime environment for our app similar to an SAP Fiori frontend server.

 

From the dropdown list, select App in FLP Sandbox (Mockserver)  and then click the button to the right with the tooltip Run.

Note if your app doesn't completely load, you might have to change the SAPUI5 version. To do so, select Run --> Run Configurations ... --> App in FLP Sandbox (Mock Server) --> Advanced Settings --> Under SAPUI5 Runtime Settings, select Use another version and select a lower one, such as 1.36.0

 

You should now see something like this:



 

Our app already contains some basic features like master/detail selection.

I hope the second part to the tutorial has sparked your interest in the chapters to come. Next time, we'll adapt the list screen of the app.
6 Comments