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: 
AndreH
Employee
Employee

BPM OData: Implementing an advanced custom task execution UI

This blog post, as part 3 of BPM OData blog series, refers to the recently introduced OData Service in SAP NetWeaver BPM available with SAP NetWeaver 7.3 EHP 1 SP 09 and higher. The features described in this blog post are available with SAP NetWeaver 7.3 EHP 1 SP 10 and higher.

Advanced UIs for the Customer Creation Process

After implementing a basic UI in the previous blog post, this blog post shows how to implement a more powerful UI using complex data types, collections and faults.

In this blog post, we use again the running example implementing the creation of a customer record in a credit institution. The data used in this example is up to now organized in a flat structure containing only primitive data types such as String or int. In more realistic scenarios you will most likely have a complex data structure containing custom data types and collections of entities. If we take a look at the example, the customer data would probably have an own complex type for the customer’s address. Moreover, a customer can have multiple phone numbers and electronic business cards (vCard), which should also be covered by the customer data. To display and edit this data in a suitable way, we will enhance the sample application accordingly as shown in Figure 1 (Tab 1).

Making the running example more realistic also includes scenarios, where you not only want to confirm a customer record, but where you might also want to reject a customer record. This could be the case, for example, if the customer record already exists in the backend. Rejecting a customer record requires the possibility to complete a task with a fault. This is supported in the newest version of the BPM OData Service and thus allows us to enhance the example accordingly as shown in Figure 1 (Tab 2).

Figure 1: Enhanced task execution UI of the task Verify Customer Data

The enhancements are divided into two steps. At first we will introduce the complex data structure and afterwards enhance the example with a fault. For both steps we will incrementally enhance the underlying scenario by adjusting the BPM Task and the used data types. With these enhancements we will have a deeper look at the technical basics when using the new features of the BPM OData Service. This helps you to better understand what’s happening behind the scenes when developing your SAPUI5 application. Finally we will enhance the existing SAPUI5 application to also make use of the new enhancements. Parts of the source code of the sample SAPUI5 application are attached to this blog post. Here is an overview about the mentioned steps covered by the following sections:

Complex data types and collections / Faults

  • Enhancing the running example
  • Technical Basics
  • Enhancing the SAPUI5 User Interface

Complex data types and collections

This section describes how to use complex data types and collections utilizing the BPM OData Service. Therefore we will enhance the underlying scenario, have a look at the technical basics and afterwards enhance the SAPUI5 application accordingly.

Enhancing the running example

The data structure previously used in the running example represented a customer record in a flat manner by using only primitive data types. This makes totally sense for simple data structures but becomes unhandy when the data structure grows and becomes more complex. Taking a look at the customer’s address shows this. For now the customer has a number of properties related to the address and these properties are grouped by having a prefix such as address-street. Separating the address into an own custom data type not only increases the readability but also the reusability. The new data type contains all the properties related to the address such as street. Instead of having a property called address-street, the customer has now a property called address using the newly defined custom data type.

Having improved the existing data structure we can now add some further information to a customer record. A customer has typically a number of phone numbers and vCards. Both types are defined within the customer as an unbounded property using minOccurs=0 and maxOccurs=unbounded, which basically means that we define a collection. A phone-number itself is a simple String while a vCard is defined using a new complex type.

Below is the enhanced XSD that is used to define the data structure:


<complexType name="Customer">
  <sequence>
    <element name="firstName" type="string"></element>
    <element name="lastName" type="string"></element>
    <element name="address" type="tns:Address"></element>
    <element name="currency" type="string" default="EUR"></element>
    <element name="phone-numbers" type="string" minOccurs="0" maxOccurs="unbounded"></element>
    <element name="vcards" type="tns:Vcard" minOccurs="0" maxOccurs="unbounded"></element>
  </sequence>
</complexType>
<complexType name="Address">
  <sequence>
    <element name="street" type="string"></element>
    <element name="city" type="string"></element>
    <element name="zip" type="integer"></element>
    <element name="country" type="string"></element>
  </sequence>
</complexType>
<complexType name="Vcard">
  <sequence>
    <element name="attr1" type="string"></element>
    <element name="attr2" type="string"></element>
    <element name="attr3" type="string"></element>
  </sequence>
</complexType>





Technical Basics

Using the enhanced data structure with the BPM OData Service works more or less the same as already described in the first blog post. The OData Service provides functionalities to access the input and output data of a task instance. Accessing for example the input data returns an entity called InputData which contains the data as specified in the BPM Process. In case of the running example we get an entity called Customer containing the data set of a customer with the first name John and the last name Doe. With the newest enhancements the entity contains also three referenced entities namely address, phone-numbers and vcards. In order to see more details of these entities, you need to expand them using the $expand operation as shown in the table below.

Expanding the entities shows that the entity address matches to the newly defined complex XSD type Address. The entity contains the defined properties with some sample data, e.g. street is set to Main St. Same is true for the entities phone-numbers and vcards. This is true for the entities phone-numbers and vcards as well, but in this case a collection is returned as defined in the XSD. For example, phone-numbers is a collection of three entities each containing a single phone number represented as a String.

The table below shows the URL used to access the input data of a BPM task along with the service response:

HTTP Method

GET

URL

… /bpmodata/taskdata.svc/e898ab9c36f611e3a6bf0000006379d2/InputData('e898ab9c36f611e3a6bf0000006379d2')?$format=json&$expand=Customer/address, Customer/phone-numbers, Customer/vcards

Response Body

(simplified)


{
  "d": {
    "Customer": {
      "firstName": "John",
      "lastName": "Doe",
      "currency": "EUR",
      "address": {
        "street": "Main str.",
        "city": "Walldorf",
        "zip": "69190",
        "country": "Germany"
      },
      "phone-numbers": {
        "results": [
          {"phone-numbers": "05567 39461"},
          {"phone-numbers": "05567 88753"},
          {"phone-numbers": "0155 1197510"}
        ]
      },
      "vcards": {
        "results": [
          {
            "attr1": "John Doe",
            "attr2": "john.doe(at)provider.com",
            "attr3": "john.doe"
          },
          {
            "attr1": "J. D.",
            "attr2": "jd(at)provider.com",
            "attr3": "jd"
          }
        ]
      }
    }
  }
}





Having a look at the Metadata Document for this task instance shows the structure of the input data including the referenced entities address, phone-numbers and vcards. As shown by the returned EDM, a complex type will be translated into an own entity type. The XSD element using such a complex type will be translated to a so-called Navigation Property which refers to the according instance of the EDM entity type. The navigation property address for example refers to the entity representing the customer's address whose entity type is Address. Navigation properties are expandable, thus you need to expand them using the $expand operation in order to access the according data.

Similar to complex types, a collection will be translated into an own entity type and a navigation property. Each navigation property is bound to a so-called Association defining the relationship between the entity types, e.g. Customer to Address. Moreover, this association defines a multiplicity for each referenced entity type. In case of a collection, this multiplicity is set to '*', which represents a one-to-many association and thus corresponds to a collection.

The table below shows the URL used to get the metadata and the corresponding response:

HTTP Method

GET

URL

…/bpmodata/taskdata.svc/e898ab9c36f611e3a6bf0000006379d2/$metadata

Response Body

(simplified)


<EntityType Name="Customer">
  <Property Name="firstName" Type="Edm.String" Nullable="true" />
  <Property Name="lastName" Type="Edm.String" Nullable="true" />
  <Property Name="currency" Type="Edm.String" Nullable="true" DefaultValue="EUR" />
  <NavigationProperty Name="address"
      Relationship="BPMTaskData.Customer_Address"
      FromRole="Customer" ToRole="Address" />
  <NavigationProperty Name="phone-numbers"
      Relationship="BPMTaskData.Customer_phone-numbers"
      FromRole="Customer" ToRole="phone-numbers" />
  <NavigationProperty Name="vcards" Relationship="BPMTaskData.Customer_Vcard"
      FromRole="Customer" ToRole="Vcard" />
</EntityType>
<EntityType Name="Address">
  <Property Name="street" Type="Edm.String" Nullable="true" />
  <Property Name="city" Type="Edm.String" Nullable="true" />
  <Property Name="zip" Type="Edm.Decimal" Nullable="true" />
  <Property Name="country" Type="Edm.String" Nullable="true" />
</EntityType>
<EntityType Name="phone-numbers">
  <Property Name="phone-numbers" Type="Edm.String" Nullable="true" />
</EntityType>
<EntityType Name="Vcard">
  <Property Name="attr1" Type="Edm.String" Nullable="true" />
  <Property Name="attr2" Type="Edm.String" Nullable="true" />
  <Property Name="attr3" Type="Edm.String" Nullable="true" />
</EntityType>
<Association Name="Customer_Address">
  <End Type="BPMTaskData.Customer" Multiplicity="1" Role="Customer" />
  <End Type="BPMTaskData.Address" Multiplicity="1" Role="Address" />
</Association>
<Association Name="Customer_phone-numbers">
  <End Type="BPMTaskData.Customer" Multiplicity="1" Role="Customer" />
  <End Type="BPMTaskData.phone-numbers" Multiplicity="*" Role="phone-numbers" />
</Association>
<Association Name="Customer_Vcard">
  <End Type="BPMTaskData.Customer" Multiplicity="1" Role="Customer" />
  <End Type="BPMTaskData.Vcard" Multiplicity="*" Role="Vcard" />
</Association>





Completing a task that contains complex properties and collections works basically the same as already described in the first blog post. You simply send an HTTP POST request to the URL which is also used to retrieve the output data. Only the data, which is sent to the service using the request body, differs slightly. The data needs to reflect the structure as defined in the metadata of the OData Service, i.e. as defined in the XSD. Therefore it also needs to contain the new entities representing the address, phone-numbers and vcards. The referenced entities are simply represented as a new JSON object, e.g. an address is a new JSON object embedded in the customer object as shown in the table below.

How does this look like in the example? Let’s imagine the used zip code is incorrect and we would like to correct it. With some experience we could write down the required data structure on our own, but in our case there is even an easier way. Since the input and output data of this task has the same structure, we can simply fetch the input data, modify it and sent it back as output data. Doing this, we can simply correct the given zip code. But be aware, that the input data entity is wrapped into a container required by OData while the output data entity is directly used without requiring this container. This means we need to remove this container when using the input data as basis for the output data.

The table below shows the URL used to complete a BPM task along with the service response:

HTTP Method

POST

URL

… /bpmodata/taskdata.svc/e898ab9c36f611e3a6bf0000006379d2/OutputData

Request Headers

Authorization

Basic dXNlcm5hbWU6cGFzc3dvcmQ=

X-CSRF-Token

781057a9-b96a-468c-b393-981f98292335

Accept

application/json

Content-Type

application/json

Request Body


{
  "Customer": {
    "firstName": "John",
    "lastName": "Doe",
    "currency": "EUR",
    "address": {
      "street": "Main str.",
      "city": "Walldorf",
      "zip": "12345",
      "country": "Germany"
    },
    "phone-numbers": {
      "results": [
        {"phone-numbers": "05567 39461"},
        {"phone-numbers": "05567 88753"},
        {"phone-numbers": "0155 1197510"}
      ]
    },
    "vcards": {
      "results": [
        {
          "attr1": "John Doe",
          "attr2": "john.doe(at)provider.com",
          "attr3": "john.doe"
        },
        {
          "attr1": "J. D.",
          "attr2": "jd(at)provider.com",
          "attr3": "jd"
        }
      ]
    }
  }
}





Response Body

(simplified)


{
  "d": {
    "Customer": {
      "firstName": "John",
      "lastName": "Doe",
      "currency": "EUR",
      "address": {
        "street": "Main str.",
        "city": "Walldorf",
        "zip": "12345",
        "country": "Germany"
      },
      "phone-numbers": {
        "results": [
          {"phone-numbers": "05567 39461"},
          {"phone-numbers": "05567 88753"},
          {"phone-numbers": "0155 1197510"}
        ]
      },
      "vcards": {
        "results": [
          {
            "attr1": "John Doe",
            "attr2": "john.doe(at)provider.com",
            "attr3": "john.doe"
          },
          {
            "attr1": "J. D.",
            "attr2": "jd(at)provider.com",
            "attr3": "jd"
          }
        ]
      }
    }
  }
}





However you construct the entity required to complete the task, it is crucial to use the correct format for your data. The OData Standard and thus the BPM OData Service is very strict when it comes to the format of the data. Frequently issues are missing brackets, too many brackets, wrong property names, etc. For example, a collection is always assigned to a property called results. Using some other name such as result (without the ending s) would lead to a failing HTTP POST request when sending this data.

Enhancing the SAPUI5 User Interface

Having the technical details in mind, we can now start to enhance the UI. The sample application was already able to display the address of a customer, but we need to adjust it with regard to our latest changes. Furthermore, it needs to be enhanced in order to show the newly introduced phone numbers and vCards.

Adjust Element Binding

The sample application is built of Panels containing TextFields. The TextFields are bound to specific properties of the OData Model. With help of an element binding, we set the binding context of the root panel. As a result, the binding of all contained TextFields can be defined relatively to the binding context, in our case the InputData.

While defining the element binding, we also specify which referenced entities should be expanded. This included so far only the referenced entity Customer. This needs to be enhanced to also include the newly introduced referenced entities address, phone-numbers and vcards.

The snippet below shows the element binding of the Panel in verifyCustomerData.controller.js:



panel.bindElement("/InputData('"+taskId+"')", {expand:"Customer/address, Customer/phone-numbers,
  Customer/vcards"});

Adjust Property Binding for Address

Expanding the referenced entities makes the according data available on the client-side so that the UI controls can access it. The property bindings of the existing UI controls in the sample application are using the shortened notation, where the binding path is specified within curly brackets as value for the field value. The path of the properties related to the customer's address changed slightly. Using the newly introduced hierarchy we have an additional path segment while the property names changed.

The snippet below shows the adjusted property binding of a TextField in verifyCustomerData.view.js:


oMatrix.createRow(
  new sap.ui.commons.Label({text : "Street", design : sap.ui.commons.LabelDesign.Bold }),
  new sap.ui.commons.TextField({value : "{Customer/address/street}"}) // property binding
);





Enhance the SAPUI5 User Interface to display phone-numbers and vcards

With these small adjustments the customer’s address is displayed correctly again. In order to display the phone numbers and vCards as well, we need to implement some new UI controls. Displaying a collection works nice using a table or a row repeater. In the example we are using a table to visualize the vcards collection. Each row of the table contains a number of TextFields to display the attributes of a specific vcard as shown in figure 2.


Figure 2: UI Control to display the collection vcards

The property binding of the TextFields is again defined using the shortened notation in curly brackets. Each row of a table is bound to a particular item of the underlying collection. This means the binding path is relative to a collection item, e.g. a vcard of the vcards collection. Therefore we can directly access the attributes of a vcard.

The snippet below shows how the table is created in verifyCustomerData.view.js:


var vcardsTable = new sap.ui.table.Table({
  visibleRowCount : 3,
  width : "35%",
  design : sap.ui.commons.LabelDesign.Bold,
  selectionMode : sap.ui.table.SelectionMode.None
});
var attr1Column = new sap.ui.table.Column({
  label : new sap.ui.commons.Label({text : "attr1"}),
  template : new sap.ui.commons.TextField({value : "{attr1}"}) // property binding
});





After creating the table it needs to be bound to the desired collection. This is done using the method bindRows. With the given parameter we define the binding path to the collection. This is again relative to the binding context of the table’s parent. Since the table is embedded within the root panel, the binding path is defined relative to the input data, e.g. Customer/vcards.

The snippet below shows how the collection is bound to the table in verifyCustomerData.view.js:

vcardsTable.bindRows("Customer/vcards");

This concludes the adjustments needed on the UI side in order to make use of the enhancements done in the scenario. The UI is now able to display complex data structures as well. Attentive readers are probably concerned about the completion of a task in SAPUI5 using such a data structure. This works the same way as already described in the previous blog post. No need to adjust anything in the SAPUI5 application, it works already out of the box for complex structures.

Faults

This section describes how to use BPM Faults utilizing the BPM OData Service. Therefore we will enhance the underlying scenario, have a look at the technical basics and afterwards enhance the SAPUI5 application accordingly.

Enhancing the running example

With help of the Custom UI Details dialog (see figure 3) the input and output type of a task can be configured as well as possible faults (Error Type). A new fault is added to the task by clicking on the button Add, assigning a name and defining the data type of the fault. In case of the running example, we add a new Fault called RejectCustomer having the custom data type Fault.

Figure 3: Dialog to enter the custom UI details

The data type of the fault is used to provide some data when completing a task with this fault (same as for output data). In case of our example, we would like to provide the possibility for the task owner to name the reason why he failed the task and thus rejected the customer record. In order to support this, we define a custom data type Fault in the XSD which contains a property called faultMessage of type String.

Below is the enhanced XSD that is used to define the data type of the fault RejectCustomer:


<complexType name="Fault">
  <sequence>
    <element name="faultMessage" type="string"></element>
  </sequence>
</complexType>





Technical Basics

The BPM OData Service supports two ways to access the faults of a BPM Task. If you do not know the available faults of a task, you can explore them using the the entity FaultData. If you already know what you are searching for, you can directly access the fault using the name of the fault.

Using the entity FaultData allows you to explore all the available faults of a specific task. The response will list the names of all assigned faults.A fault itself contains an empty data object, since this data is only set when completing the task with the fault. If you expand the fault RejectCustomer you can explore the according data object which corresponds to the previously defined Fault.

The table below shows the URL used to access the entity FaultData of a BPM task along with the service response:

HTTP Method

GET

URL

… /bpmodata/taskdata.svc/e898ab9c36f611e3a6bf0000006379d2/FaultData('e898ab9c36f611e3a6bf0000006379d2')?$format=json&$expand=RejectCustomer/Fault

Response Body

(simplified)


{
  "d": {
    "RejectCustomer": {
      "Fault": {
        "faultMessage": null
      }
    }
  }
}





Once you know the name of your desired fault, you can directly access it using this name. The response contains again the according data object. The table below shows the URL used to access a specific fault of a BPM task along with the service response:

HTTP Method

GET

URL

/bpmodata/taskdata.svc/e898ab9c36f611e3a6bf0000006379d2/RejectCustomer('e898ab9c36f611e3a6bf0000006379d2')?$format=json&$expand=Fault

Response Body

(simplified)


{
  "d": {
    "Fault": {
      "faultMessage": null
    }
  }
}





Completing a task with a fault works basically the same way as completing a task regularly. You just send an HTTP POST request to the URL that is used to directly access a fault by name. The request body contains the data as defined by the type of the fault, in our example a Fault object containing a faultMessage. The response body contains the actual BPM fault that was created on the server in order to fail the task.

The table below shows the URL used to complete a BPM task with a fault along with the service response:

HTTP Method

POST

URL

… /bpmodata/taskdata.svc/e898ab9c36f611e3a6bf0000006379d2/RejectCustomer

Request Headers

Authorization

Basic dXNlcm5hbWU6cGFzc3dvcmQ=

X-CSRF-Token

781057a9-b96a-468c-b393-981f98292335

Accept

application/json

Content-Type

application/json

Request Body


{
  "Fault": {
    "faultMessage": "Customer already exists."
  }
}





Response Body

(simplified)


{
  "d": {
    "Fault": {
      "faultMessage": "Customer already exists."
    }
  }
}





Enhancing the SAPUI5 User Interface

Based on the technical details learned in the previous section, we can now implement the UI accordingly. Rejecting a customer was not yet considered in the sample application. Therefore we need to add some UI controls including the according bindings. Furthermore, we need to implement the functionality to finally perform the HTTP POST request in order to fail the task.

On the UI side we introduce a new Tab containing a root panel as shown in figure 4. This panel contains a TextArea bound to the faultMessage and a Button which triggers the post request to fail the task.

Figure 4: UI Control to complete the task Verify Customer Data with the fault Reject Customer

The implementation of the UI works pretty much the same as already done for the input data. On the root panel we define again an element binding as you already know from the input data. But this time the panel is bound to a specific fault, in our case RejectCustomer.

The snippet below shows the element binding of the Panel in verifyCustomerData.controller.js:

faultPanel.bindElement("/RejectCustomer('" + taskId + "')", {expand : "Fault" });

Having done this, we can create the TextArea and bind it relatively to the fault. This means we can directly use the data type Fault as starting point for the binding path. In our case the TextArea is bound to the faultMessage.

The snippet below shows how the controls are created in verifyCustomerData.view.js:


oMatrixLayout.createRow(
  new sap.ui.commons.Label({text : "Reason", design : sap.ui.commons.LabelDesign.Bold }),
  new sap.ui.commons.TextArea({value : "{Fault/faultMessage}", rows : 2, cols : 60})
);





After implementing the UI and defining the bindings, the sample application is able to gather the needed fault data from the end user and, thanks to the experimental Two-Way Binding, pushes this data directly back to the ODataModel. In order to finally complete the task, we just fetch this data from the ODataModel, create the required fault data and send a post request based on the RejectCustomer fault. This works again basically the same as completing a task in a regular way.

The snippet below shows how to complete a task with a fault in verifyCustomerData.controller.js:


var faultData = {};
var fault = odataModel.getProperty("/RejectCustomer('" + taskId + "')/Fault");
faultData.Fault = fault;
odataModel.create("/RejectCustomer", faultData);





Conclusion

This part has shown the implementation of a more advanced custom task execution UI for a BPM task. This included the usage of complex data types and collections as well as completing a task with a fault. The technical details of these functionalities have been described and the sample SAPUI5 application has been enhanced accordingly.

2 Comments