This blog post, as part 2 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 BPM OData Service provides an easy access to BPM related data and operations and thus accelerates, for example, the implementation of a custom task execution UI with SAPUI5. After exploring the service in the previous blog post, this blog post shows how to implement such a UI utilizing the BPM OData Service.
If you have not implemented a custom task execution UI before, take a look at the help portal Assigning a Custom User Interface to a Task. If this is going to be your first SAPUI5 application, check out the blog posts Get to Know the UI Development Toolkit for HTML5 (aka SAPUI5) and Developing SAP UI5 applications in SAP NetWeaver Developer Studio.
In this blog post, we use again the running example, which implements a simple scenario where a customer of a credit institution is created in the system. Using, for example, a self-service, the new customer provides his data and implicitly starts the process along with this data. The process includes a task to verify the given customer data and a task to add a credit limit. Both tasks will be equipped with a custom task execution UI as shown in Figure 1.
Figure 1: BPMN process model implementing the exemplary scenario with custom task execution UIs
The UI of the first task Verify Customer Data displays the given customer data in a simple form design. The owner of this task is able to not only view the data but also to correct it in case of any mistakes. Therefore, the fields are editable. A click on the Confirm button completes the task with the potentially corrected data.
Having the confirmed customer data, a financial specialist is now able to grant a credit limit to the customer using the second task Add Credit Limit. Therefore, the UI of this task displays the data of the particular customer and provides an input field to enter the credit limit. Clicking the Add Credit Limit button completes the task with the entered credit limit.
This blog post describes now how to create the two mentioned UIs. This is done with focus on the first UI. The second UI is explained later focusing on the differences between the implementation of both UIs. Parts of the source code of the sample SAPUI5 application are attached to this blog post.
Implementing a custom task execution UI involves again the whole round trip for a BPM task from A to Z:
In order to initialize the UI with some data, the input data of the task is fetched and displayed by the controls of the UI. Opening the UI could directly claim the task, so that the task is automatically reserved for the user opening the task execution UI. After the user has worked on the task and potentially provided modifications or additions on the data, the UI implementation completes the task by providing the values for the task’s output data.
Here is an overview about the mentioned steps:
The following sections describe how you can implement a SAPUI5 application executing the mentioned actions with the BPM OData Service.
It all starts with the UI. The UI can be developed using some UI Builder or following some of the available tutorials. You could even implement a UI skeleton fitting to the task data without any further knowledge of the OData Service that is later connected to the UI. If you finally have a nice task execution UI, this UI needs to be wired to the OData service. Therefore, an OData-specific model is created, which is connected to the BPM OData Service. After creating this model, the UI controls are bound to properties of the model. The data is automatically fetched by SAPUI5 according to the defined bindings. That’s it. Easy as 1-2-3:
Previous to the BPM OData Service, you may have developed a custom UI using the BPM Public Java API. With modern Web technologies like JavaScript or SAPUI5, the UIs are rendered at client-side. In such a case, you cannot directly access the BPM Public Java API to access data and functionalities from BPM. The BPM OData Service is a built-in remote service, which offers selected functionalities of the BPM Public Java API. Using this service you do not need to implement own server-side Java applications anymore.
Let’s start to build the UI. The sample application is implemented according to the Model-View-Controller (MVC) paradigm, where the model represents the application data, the view renders this data, and the controller modifies both according to the users’ interaction. Following this concept, the view and the controller are clearly separated from each other and even implemented in different files. In case of the sample application, the view is constructed in a traditional manner using a JavaScript View (JSView Definition) in the file verifyCustomerData.view.js
. The controller of this view is defined in the file verifyCustomerData.controller.js
. The model is obviously the OData-specific model which is created later. More information on MVC in SAPUI5 can be found in the blog post How to Write Testable SAPUI5 Applications.
The sample UI uses a Panel containing some Label/TextField combinations arranged by a MatrixLayout. The snippet below shows how the controls of this UI are created in verifyCustomerData.view.js
:
var oPanel = new sap.ui.commons.Panel({
text: "Customer Data",
showCollapseIcon: false,
areaDesign: sap.ui.commons.enums.AreaDesign.Plain
});
var oMatrix = new sap.ui.commons.layout.MatrixLayout({
layoutFixed: false,
widths:["10%", "90%"],
columns: 2
});
oMatrix.createRow(
new sap.ui.commons.Label({text : "First Name", design : sap.ui.commons.LabelDesign.Bold }),
new sap.ui.commons.TextField()
);
// etc.
oPanel.addContent(oMatrix);
Having this UI skeleton, the UI can now be brought to life by connecting it to the OData Service. The first step is to create a model instance in the controller of the view. In case of OData, SAPUI5 provides a specialized model, the ODataModel. This model is connected to a defined OData Service and retrieves only the data that is requested by the UI. Since we are interested in the input data of a task, the model is bound to the BPM Task Data OData Service. In order to load the needed data, SAPUI5 sends automatically requests to the BPM OData Service, which are basically the same as described in the previous blog post.
The constructor of the ODataModel has the service URL as its first parameter. In case of the example, the base URL of the OData Service is specified as parameter of the constructor. The URL corresponds to /bpmodata/taskdata.svc/<task_instance_id>
, where <task_instance_id>
is the task instance ID of the particular BPM task. In case of displaying the task execution UI out of the BPM Inbox, this ID is passed to the UI using the HTTP parameter taskId
.
The snippet below shows the creation of the ODataModel in verifyCustomerData.controller.js
:
var taskId = getValueOfURLParameter("taskId");
var taskDataSvcURL = "/bpmodata/taskdata.svc/"+taskId;
var taskDataODataModel = new sap.ui.model.odata.ODataModel(taskDataSvcURL, true);
In the sample application, the created ODataModel is not only used to fetch some data but also to store the modified data, i.e. to write user-entered values from the UI controls to the model. For that purpose, the experimental Two Way Binding is enabled for the model. Using this binding mode, SAPUI5 ensures that values changed in the view are automatically pushed to the model and vice versa. Since this feature is currently only experimental it might change over time! Other options are discussed later.
The snippet below shows how to configure the Two Way Binding in verifyCustomerData.controller.js
:
taskDataODataModel.setDefaultBindingMode(sap.ui.model.BindingMode.TwoWay);
After creating the model, it needs to be introduced to the framework, so that every UI control looking for some data knows to whom to speak. In case a UI control is looking for data, it searches for a model by looking up the parent hierarchy until it finds an ancestor having an assigned model. It is possible to define either a global or a control-specific model. A global model can be accessed by all controls from all UI areas while a control-specific model can only be accessed in a local context by the particular control and all embedded controls.
Using a control-specific model makes it possible to have multiple models in a single application. As you might remember from the previous blog post, technically the BPM OData Service is implemented as multiple services. Since one ODataModel instance can only cover a single OData Service, it might be needed to create multiple models. This is not done in this blog post, but it is already considered when creating and setting the first model.
A control-specific model can be set to any control using the method setModel
. In case of the example, the model is set to the view representing the task execution UI.
The snippet below shows how the control-specific model is assigned in verifyCustomerData.controller.js
:
this.getView().setModel(oModel);
In order to finally retrieve some data from the model for visualization in the UI, the UI controls have to be bound to properties of the model. For this purpose, the data provided by the previously created ODataModel is accessed. In the background, SAPUI5 fetches the needed data by automatically sending requests to the OData Service according to the defined bindings of the UI controls.
In case of the sample UI, there are several UI controls visualizing properties of one and the same object in the model, i.e. the InputData
. This means, that the binding definition of these UI controls will look similar. In order to simplify the definition of the binding, SAPUI5 allows binding a model object to a specific UI Control using an element binding. This creates a binding context and allows relative bindings within the control and all of its children. In case of our server-side ODataModel, the element binding is triggering the request to the OData Service to load the referenced object in case it is not yet available on the client. In case of the sample UI, the InputData
is bound to the Panel using such an element binding, so that the binding of all contained TextFields can be defined relatively to the InputData
.
This binding is done using an OData-specific binding path. This path defines how a specific model object, e.g., the InputData
can be found within the model. This is done accordingly to the structure of the OData Service as defined in the metadata of the service. The binding path is relative to the service URL defined with the ODataModel and contains a number of name tokens separated by a separator char. For example, /InputData(‘<taskId>’)
, where <taskId>
is the task instance ID of a task.
In order to finally define the element binding for the Panel, the method bindElement
is called on the Panel. The function takes two parameters: the binding path and a map of additional binding parameters. The binding parameters are later transformed into OData query options. In case of an OData Service, this is especially interesting for the expand option. As you might remember, the OData $expand
query option is used to specify that referenced entities should be represented inline within the service response. Omitting the expand option would result in a service response containing only the wrapper element InputData
without any further data. In the example, the OData $expand
option is used to expand the content of a customer data.
The snippet below shows the element binding of the Panel in verifyCustomerData.controller.js
:
panel.bindElement("/InputData('"+taskId+"')", {expand:"Customer"});
Now, after the wrapper element InputData
is bound to the Panel, the UI controls of the Panel can be bound to properties of this wrapper element in order to visualize the data. For this purpose, the property binding is used. This binding allows UI controls to be automatically initialized with the model data and updated when the data is modified.
The property binding of a control can be defined within the settings object in the constructor of a control or later using the method bindProperty
. When defining the property binding in the constructor, a shortened notation can be used. Using this notation the binding path is specified within curly brackets as value for the field value
in the setting object of the UI control. For example, {Customer/firstName}
to access the property firstName
of the element Customer
relative to the bound element of the parent object, i.e. InputData
bound to the Panel. Once the property binding is defined, the UI control will be automatically updated according to this binding whenever the value of the bound property in the model is changed.
The snippet below shows the property binding of a TextField in verifyCustomerData.view.js
:
oMatrix.createRow(
new sap.ui.commons.Label({text : "First Name", design : sap.ui.commons.LabelDesign.Bold }),
new sap.ui.commons.TextField({value : "{Customer/firstName}"}) // property binding
);
Having a task execution UI that is able to represent the given input data lays the foundations for further enhancements. The next step is, not only to view and modify the input data of a task but also to complete the task with the modified data. You may remember that, with posting the output data of the task to the OData Service, the BPM task gets completed implicitly.
So, how do we get the needed output data to complete the task? The needed data corresponds to the data displayed in the UI. Using the property binding and the Two Way Binding of the ODataModel, the modified data is automatically pushed back to the model, more precisely to the input data object of the model. But the changes only affect the model in the browser so far and they are not posted to the service. Furthermore, the changes are still only reflected in the input data and not in the needed output data.
Luckily, the task uses the same data types for its input and output data. This allows us to create the Output Data Object including the modified data out of the Input Data Object from the ODataModel. This newly created Output Data Object can then be used as payload to complete the task.
The snippet below shows how to create the needed Output Data Object in verifyCustomerData.controller.js
:
var outputData = {};
var customer = odataModel.getProperty("/InputData('"+taskId+"')/Customer");
outputData.Customer = customer;
Alternatively to the proposed solution, you could also implement the UI without the Two Way Binding. Omitting the Two Way Binding has the drawback that you need to grab the changed values from the UI controls on your own. Another option would be to implement a read-only view for the input data and an empty view for the output data. This is done later with the second UI, due to the different data types of the input and output data. In case of our first UI, where we would like to modify the given input data, this would basically lead to duplicated UI controls for both the input and the output data. This would be anything but a stunning UI.
Having a suitable Output Data Object, it is only a small step to complete the task with this data. Therefore, the write support of the ODataModel is used by invoking the method create
and passing the Output Data Object as parameter. As the ODataModel is a server-side model, this will trigger an HTTP POST request to the BPM OData Service specified when creating the model. The first parameter of the method is used to specify the relative binding path describing where to create the entry. The second parameter contains the payload. Invoking the method triggers the POST request to complete the task with the output data within the request body.
The snippet below shows how to trigger the request to complete a task in verifyCustomerData.controller.js:
odataModel.create("/OutputData", outputData);
The snippet above triggers an HTTP POST request, which can be considered as a write operation. You might remember that in case of a write operation, additional security considerations should be taken into account to secure BPM process data. The BPM OData Service is using a CSRF token to prevent CSRF attacks. When exploring the service in the previous blog post, you needed to explicitly handle these tokens. Using SAPUI5 and its ODataModel, this is done automatically.
In SAP NetWeaver BPM, a task cannot be completed if it is not claimed before. In order to improve the efficiency and convenience of your end users, you could automatically claim the task for the user when opening the task execution UI.
You might remember that claiming a task is done with help of the BPM Tasks OData Service using the OData resource path Claim.
The task instance ID of the task is passed with the parameter InstanceID
. Since this particular service was not used before in the sample application, a new ODataModel bound to the service needs to be created.
The snippet below shows the creation of the ODataModel in utility.js
:
var tasksSvcURL = "/bpmodata/tasks.svc";
var tasksODataModel = new sap.ui.model.odata.ODataModel(tasksSvcURL, false);
Having an ODataModel bound to the BPM Tasks OData Service, the request to claim a task can be sent to the service. Therefore, the method create
is used again, but this time based on the new model. The first parameter of the method is again used to specify the relative binding path including the parameter InstanceID
. The second parameter is empty this time, because the request to claim a task does not require any payload.
The snippet below shows how to trigger the request to claim a task in utility.js
:
var taskId = getValueOfURLParameter("taskId");
tasksODataModel.create("/Claim?InstanceID='"+taskId+"'", null);
After having implemented the task execution UI for the first task, it‘s a simple job to create the UI for the second task called Add Credit Limit. There is only one minor difference between both tasks, which changes not the overall story but some details of the implementation. As described when introducing the running example, the task Add Credit Limit uses different data types for its input and output data. As a result, the input data cannot be reused when creating the output data object and triggering the request to complete the task. This section describes what needs to be done to also get this scenario up and running.
It starts again with the creation of the UI. The basic setup stays unchanged. MVC is used having separated files for both, the controller and the view. The procedure to create the ODataModel is the same as already described.
In order to visualize both, the input data and the output data, different UI controls are used. Because the input data for this task has the same type as the one for the task Verify Customer Data, the same UI controls can be used. Since the input data should be treated as read-only in this case, the TextFields are not editable. To also display the output data, i.e. the granted credit limit, a new Panel containing a further Label/TextField combination is introduced.
The snippet below shows how the controls of this UI are created in addCreditLimit.view.js
:
var creditPanel = new sap.ui.commons.Panel({
text: "Credit",
showCollapseIcon: false,
areaDesign: sap.ui.commons.enums.AreaDesign.Plain
});
var oMatrix = new sap.ui.commons.layout.MatrixLayout({
layoutFixed: false,
widths:["10%", "90%"],
columns: 2
});
oMatrix.createRow(
new sap.ui.commons.Label({text : "Credit Limit", design : sap.ui.commons.LabelDesign.Bold }),
new sap.ui.commons.TextField({value : "{Credit/creditLimit}"})
);
// etc.
creditPanel.addContent(oMatrix);
The newly created Panel is bound to the output data of the task. The output data has the type Credit
, which contains only the property creditLimit
. The TextField embedded in the Panel is bound to the property creditLimit
using a property binding in the constructor. Again, the Two Way Binding takes care that any modification of the data triggered by the user is propagated to the corresponding entity in the ODataModel. Therefore, the output data of the ODataModel is always up-to-date.
The snippet below shows the element binding of the View in addCreditLimit.controller.js
:
customerPanel.bindElement("/InputData('"+taskId+"')", {expand:"Customer"});
creditPanel.bindElement("/OutputData('"+taskId+"')", {expand:"Credit"});
When finally completing the task, the same POST request is sent to the BPM OData Service. But rather than reusing the input data, the output data is used as payload.
The snippet below shows how to complete a task in addCreditLimit.controller.js
:
var outputData = {};
var credit = odataModel.getProperty("/OutputData('"+taskId+"')/Credit");
outputData.Credit = credit;
odataModel.create("/OutputData", outputData);
This part has shown the implementation of a basic custom task execution UI for a BPM task. It was described how to implement such a UI as SAPUI5 application for a typical scenario. This included the access to the input data of a task as well as activities for claiming and completing a task. A basic UI has been implemented in SAPUI5 along with an ODataModel whose properties are bound to UI controls.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
26 | |
24 | |
21 | |
13 | |
9 | |
9 | |
9 | |
9 | |
8 | |
8 |