Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
Former Member

Hi

My name is Jan Nyfeler.

Today I'd like to write about SAP NW BPMs new OData-Services for Completing Tasks and - new - starting Processes. Another concern I'd like to discuss is reusability and componentization within sapui5.

I've read the excellent tutorials about the odata task service and the task data odata service from Andre Backofen (Custom UIs with the BPM OData Service and BPM OData: Implementing a Basic Custom Task Execution UI) as well as the blog series about sapui5 and nwds / nwdi by Christian Loos (Developing SAP UI5 applications in SAP NetWeaver Developer Studio - Part 1/2)

You can find the official SAP Help here OData Service for Accessing BPM Task Data - Modeling Processes with Process Composer - SAP Library and here BPM OData Service for Starting BPM Processes - Modeling Processes with Process Composer - SAP Librar....

You can create your project / DC setup according to the blog post by Christian Loos. I came across a couple of issues I had to solve differently or in addition to Christians explenations:

DCs

In addition to the web module, you need an Java EE / Enterprise Application Archive (ear). Within this ear-DCs META-INF directory you'll need a file named application.xml. In my case it has the following content:


<?xml version="1.0" encoding="ASCII"?>
<application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:application="http://java.sun.com/xml/ns/javaee/application_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application_5.xsd" version="5">
  <display-name>pr~sapui5~demo~ear~novobc.ch</display-name>
  <module>
    <web>
      <web-uri>novobc.ch~pr~sapui5~demo.war</web-uri>
      <context-root>pr~sapui5~demo~novobc.ch</context-root>
    </web>
  </module>
</application>

In the web module there is a web.xml file. I don't need the extra content as described by Christian. My web.xml looks like


<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>pr~sapui5~demo~novobc.ch</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
</web-app>

Afterwards you're ready to go.

In my example I'd like to create a very simple approval-process. Therefor we'll need three DCs. You can name them as your companies naming convention ask you to. In my case the three DCs are:

  • Web Module (pr/sapui5/demo)
  • Enterprise Archive (pr/sapui5/demo/ear)
  • Process Composer (pr/pm/demo)

Process

My process looks like this:

However I'm going to cover only the process start application as well as the user interface for the activity "Antrag stellen". The idea is the following: an employee can start a process and check it in this activity. After completing the task, the process is forwarded to the employees supervisor for approval.

The web service used for starting a process looks like this:


<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://www.example.org/Start_Bewilligung_Async/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="Start_Bewilligung_Async" targetNamespace="http://www.example.org/Start_Bewilligung_Async/">
  <wsdl:types>
    <xsd:schema targetNamespace="http://www.example.org/Start_Bewilligung_Async/" xmlns:Q1="http://www.example.org/Antrag">
            <xsd:import schemaLocation="Antrag.xsd" namespace="http://www.example.org/Antrag"></xsd:import>
            <xsd:element name="StartBewilligung">
        <xsd:complexType>
          <xsd:sequence>
            <xsd:element name="Antrag" type="Q1:Antrag"/>
          </xsd:sequence>
        </xsd:complexType>
      </xsd:element>
    </xsd:schema>
  </wsdl:types>
  <wsdl:message name="StartBewilligungRequest">
    <wsdl:part element="tns:StartBewilligung" name="parameters"/>
  </wsdl:message>
  <wsdl:portType name="Start_Bewilligung_Async">
    <wsdl:operation name="StartBewilligung">
      <wsdl:input message="tns:StartBewilligungRequest"/>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="Start_Bewilligung_AsyncSOAP" type="tns:Start_Bewilligung_Async">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="StartBewilligung">
      <soap:operation soapAction="http://www.example.org/Start_Bewilligung_Async/StartBewilligung"/>
      <wsdl:input>
        <soap:body use="literal"/>
      </wsdl:input>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="Start_Bewilligung_Async">
    <wsdl:port binding="tns:Start_Bewilligung_AsyncSOAP" name="Start_Bewilligung_AsyncSOAP">
      <soap:address location="http://www.example.org/"/>
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

The correspondig Antrag-Datatype looks like this:


<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.org/Antrag" xmlns:tns="http://www.example.org/Antrag$" elementFormDefault="qualified">
    <complexType name="Antrag">
    <sequence>
    <element name="Mitarbeiter" type="string"></element>
    <element name="Vorgesetzter" type="string"></element>
    <element name="Titel" type="string"></element>
    <element name="Beschreibung" type="string"></element>
    <element name="Status" type="string"></element>
    </sequence>
    </complexType>
</schema>

SAPUI5

Icons

Check out the sap-icon://-Protocol. It's a great and easy way to include icons into your views....

Views

SAPUI5 offers different ways of creating views (JavaScript, JSON, HTML, XML). I usually choose to create XML view as it is the (for me personally) most self-explanatory way and it's very easy to bind data to input elements.

In addition, I've found this tool (Fast SAPUI5 Develop tool) by Lucky Li for creating views - as there is no WYSIWYG-Editor for SAPUI5 (See wish list down below) so far this might help others.

Form-Fragments

I'm a lazy guy and don't like to do stuff twice 😉

Web Dynpro has a great componentization approach - you create a component and reuse it as many time as you want to. By Context-Mapping and public methods you can interact with the reused components in every way you need to.

I discovered the fragment-approach in SAPUI5: this enables you to create a UI-Fragment and reuse this fragment in different views. Not exactly the same comfort as in Web Dynpro, but it's a good beginning.

My fragment for the approval process looks like this (Antrag.fragment.xml):


<core:FragmentDefinition
  xmlns="sap.m"
  xmlns:l="sap.ui.layout"
  xmlns:f="sap.ui.layout.form"
  xmlns:core="sap.ui.core">
  <Panel id="FragmentPanel">
  <l:Grid
    defaultSpan="L12 M12 S12"
    width="auto">
    <l:content>
    <f:SimpleForm id="Formular"
        minWidth="1024"
        maxContainerCols="2"
        editable="true"
        layout="ResponsiveGridLayout"
        title="Antrag"
        labelSpanL="4"
        labelSpanM="4"
        emptySpanL="0"
        emptySpanM="0"
        columnsL="2"
        columnsM="2"
        class="editableForm">
        <f:content>
          <core:Title text="Antragsdaten" />
          <Label text="Titel" />
          <Input id="Titel" value="{Antrag/Titel}" />
          <!--<Label text="Datum" />
    <DateTimeInput type="Date">
      <layoutData>
              <l:GridData span="L4 M4 S4" />
          </layoutData>
          </DateTimeInput> -->
        <!-- <Label text="Kosten" />
          <Input id="Kosten" value="{Antrag/Kosten}" type="Number">
          <layoutData>
              <l:GridData span="L4 M4 S4" />
          </layoutData>
          </Input> -->
          <Label text="Status" />
          <Select id="Status" selectedKey="{Antrag/Status}" width="100%">
            <items>
              <core:Item text="" key="" />
              <core:Item text="Entwurf" key="Entwurf" />
              <core:Item text="Beantragt" key="Beantragt" />
              <core:Item text="Bewilligt" key="Bewilligt" />
              <core:Item text="Korrektur" key="Korrektur" />
              <core:Item text="Abgelehnt" key="Abgelehnt" />
            </items>
          </Select>
          <Label text="Beschreibung" />
          <TextArea id="Beschreibung" value="{Antrag/Beschreibung}" rows="8" />
          <core:Title text="Beteiligte" />
          <Label text="Mitarbeiter" />
          <Input id="Mitarbeiter" value="{Antrag/Mitarbeiter}" />
          <Label text="Vorgesetzter" />
          <Input id="Vorgesetzter" value="{Antrag/Vorgesetzter}" />
        </f:content>
      </f:SimpleForm>
    </l:content>
  </l:Grid>
  </Panel>
</core:FragmentDefinition>

The view for my Start application looks like this:


<core:View xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" xmlns:c="sap.ui.commons"
  controllerName="demo.StartAntrag" xmlns:l="sap.ui.layout" xmlns:f="sap.ui.layout.form" xmlns:html="http://www.w3.org/1999/xhtml">
  <Panel id="Panel">
  </Panel>
      <Bar>
        <contentRight>
          <Button id="Abschliessen" text="Bewilligungsprozess starten" press="handleAbschliessenPress" />
        </contentRight>
      </Bar>
</core:View>

And the view for the Task looks like this:


<core:View xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" xmlns:c="sap.ui.commons"
  controllerName="demo.AntragM" xmlns:l="sap.ui.layout" xmlns:f="sap.ui.layout.form" xmlns:html="http://www.w3.org/1999/xhtml">
  <Panel id="Panel">
  </Panel>
      <Bar>
        <contentRight>
          <Button id="Abschliessen" text="Abschliessen" press="handleAbschliessenPress" />
        </contentRight>
      </Bar>
</core:View>

For both views the fragment is inserted at onInit() in the corresponding controller.

Start Process

There are new odata-services for working with sap nw bpm processes.

The SAP help says, that first a X-CSRF-Token need to be fetched (GET-Request) and afterwards a POST request should be submitted in order to start a process. Using SAPUI5 OData-Model the code would look similar to this:


var startProcessSvcURL = "/bpmodata/startprocess.svc/novobc.ch/pr~pm~demo/Bewilligung";
            var startData = {};
            startData.ProcessStartEvent = {};
            startData.ProcessStartEvent.Antrag = {};//Form binding;
oModel.create("/StartData", startData, null, function(
                                               oData, oResponse) {
                                         // message
                                         }, function(oEvent) {
                                               sap.m.MessageBox.show("Beim Start des Prozesses ist ein Fehler aufgetreten.",
                                                           sap.m.MessageBox.Icon.ERROR,
                                                           "Fehler beim Prozessstart");
                                         });

However we were not getting this to work as the initial GET-request in order to fetch the Token is sent to an invalid url.

It tries to get from ../bpmodata/startprocess.svc/novobc.ch/pr~pm~demo/Bewilligung which is the root url used to initialize the OData-Model. As the Post (oModel.create()) is submitted to /bpmodata/startprocess.svc/novobc.ch/pr~pm~demo/Bewilligung/StartData it should also try to fetch the token from this url. However, we were not able to get it working correctly, but Andre Backofen was a great support and offered to investigate our issue further.

To get the demo process working, we're avoiding SAPUI5 OData-Model in this particular case and are going JQuery: First, we're fetching the Token by GET.

Afterwards we reuse the fetched token and set it to the post request's header.


$.ajax({
    type: 'GET',
    url: 'http://s083.novo-bc.ch:50000/bpmodata/startprocess.svc/novobc.ch/pr~pm~demo/Bewilligung/StartData',
    beforeSend: function(requestGET){requestGET.setRequestHeader('X-CSRF-Token', 'Fetch');},
    success: function(data, textStatus, requestGET){
    $.ajax({
   type: 'POST',
   url: 'http://s083.novo-bc.ch:50000/bpmodata/startprocess.svc/novobc.ch/pr~pm~demo/Bewilligung/StartData',
   beforeSend: function(requestPOST){
  requestPOST.setRequestHeader('X-CSRF-Token', requestGET.getResponseHeader('X-CSRF-Token'));
  requestPOST.setRequestHeader('Content-Type', 'application/json');
    },
   data: startData,
   success: function () {sap.m.MessageBox.show("Der Prozess wurde erfolgreich gestartet. Sie können das Fenster schliessen.",
  sap.m.MessageBox.Icon.SUCCESS,
  "Prozess erfolgreich gestartet");}
  });
    }
   });

Here is the complete code of the working controller - I'm aware that it won't win a beauty contest (especially how I created the payload), but it's working and starts the demo process:


jQuery.sap.require("demo.Util");
jQuery.sap.require("sap.m.MessageBox");
sap.ui
  .controller(
  "demo.StartAntrag",
  {
  getFormFragment : function() {
  return sap.ui.xmlfragment("demo.Antrag", this);
  },
  /**
  * Called when a controller is instantiated and its View
  * controls (if available) are already created. Can be used
  * to modify the View before it is displayed, to bind event
  * handlers and do other one-time initialization.
  *
  * @memberOf demo.Antrag
  */
  onInit : function() {
  this.oForm = this.getFormFragment();
  var oPanel = this.getView().byId("Panel");
  oPanel.removeContent(0);
  oPanel.insertContent(this.oForm);
  var oMitarbeiter = sap.ui.getCore().byId("Mitarbeiter");
  oMitarbeiter.setValue("Nyfeler Jan");
  oMitarbeiter.setEnabled(false);
  var oVorgesetzter = sap.ui.getCore().byId("Vorgesetzter");
  oVorgesetzter.setValue("Nyfeler Jan");
  oVorgesetzter.setEnabled(false);
  var oStatus = sap.ui.getCore().byId("Status");
  oStatus.setSelectedKey("Entwurf");
  oStatus.setEnabled(false);
  },
  /**
  * Similar to onAfterRendering, but this hook is invoked
  * before the controller's View is re-rendered (NOT before
  * the first rendering! onInit() is used for that one!).
  *
  * @memberOf demo.Antrag
  */
  // onBeforeRendering: function() {
  //
  // },
  /**
  * Called when the View has been rendered (so its HTML is
  * part of the document). Post-rendering manipulations of
  * the HTML could be done here. This hook is the same one
  * that SAPUI5 controls get after being rendered.
  *
  * @memberOf demo.Antrag
  */
  // onAfterRendering: function() {
  //
  // },
  /**
  * Called when the Controller is destroyed. Use this one to
  * free resources and finalize activities.
  *
  * @memberOf demo.Antrag
  */
  // onExit: function() {
  //
  // }
  handleAbschliessenPress : function() {
  var that = this;
  var startProcessSvcURL = "/bpmodata/startprocess.svc/novobc.ch/pr~pm~demo/Bewilligung";
  var oModel = new sap.ui.model.odata.ODataModel(startProcessSvcURL, true);
  var startData = '{"vendor":"novobc.ch","dcName":"pr~pm~demo","processTechnicalName":"Bewilligung","ProcessStartEvent":{"::StartBewilligung":{"Antrag":{"Mitarbeiter":"MitarbeiterValue","Vorgesetzter":"VorgesetzterValue","Titel":"TitelValue","Beschreibung":"BeschreibungValue","Status":"StatusValue"}}}}';
  var Mitarbeiter = sap.ui.getCore().byId("Mitarbeiter").getValue();
  var Vorgesetzter = sap.ui.getCore().byId("Vorgesetzter").getValue();
  var Titel = sap.ui.getCore().byId("Titel").getValue();
  var Beschreibung = sap.ui.getCore().byId("Beschreibung").getValue();
  var Status = sap.ui.getCore().byId("Status").getSelectedKey();
  startData = startData.replace("MitarbeiterValue", Mitarbeiter);
  startData = startData.replace("VorgesetzterValue", Vorgesetzter);
  startData = startData.replace("TitelValue", Titel);
  startData = startData.replace("BeschreibungValue", Beschreibung);
  startData = startData.replace("StatusValue", Status);
  $.ajax({
    type: 'GET',
    url: 'http://s083.novo-bc.ch:50000/bpmodata/startprocess.svc/novobc.ch/pr~pm~demo/Bewilligung/StartData',
    beforeSend: function(requestGET){requestGET.setRequestHeader('X-CSRF-Token', 'Fetch');},
    success: function(data, textStatus, requestGET){
    $.ajax({
  type: 'POST',
  url: 'http://s083.novo-bc.ch:50000/bpmodata/startprocess.svc/novobc.ch/pr~pm~demo/Bewilligung/StartData',
  beforeSend: function(requestPOST){
    requestPOST.setRequestHeader('X-CSRF-Token', requestGET.getResponseHeader('X-CSRF-Token'));
    requestPOST.setRequestHeader('Content-Type', 'application/json');
    },
  data: startData,
  success: function () {sap.m.MessageBox.show("Der Prozess wurde erfolgreich gestartet. Sie können das Fenster schliessen.",
  sap.m.MessageBox.Icon.SUCCESS,
  "Prozess erfolgreich gestartet");}
  });
    }
  });
  }
  });

Working with Tasks

Working with bpm tasks within SAPUI5 is very easy and I mostly referr to Andre Backofens Blog Posts.

This is the code of the task controller:


jQuery.sap.require("demo.Util");
jQuery.sap.require("sap.m.MessageBox");
sap.ui.controller("demo.AntragM", {
  getFormFragment: function () {
    return sap.ui.xmlfragment("demo.Antrag", this);
  },
/**
* Called when a controller is instantiated and its View controls (if available) are already created.
* Can be used to modify the View before it is displayed, to bind event handlers and do other one-time initialization.
* @memberOf demo.AntragM
*/
  onInit: function() {
    oForm = this.getFormFragment();
    var oPanel = this.getView().byId("Panel");
    oPanel.removeContent(0);
    oPanel.insertContent(oForm);
  this.taskId = getValueOfURLParameter("taskId");
  //alert(this.taskId);
  var taskDataSvcURL = "/bpmodata/taskdata.svc/" + this.taskId;
  var taskDataODataModel = new sap.ui.model.odata.ODataModel(taskDataSvcURL, true);
  taskDataODataModel.setDefaultBindingMode(sap.ui.model.BindingMode.TwoWay);
  sap.ui.getCore().setModel(taskDataODataModel);
  oForm.setModel(taskDataODataModel);
  sap.ui.getCore().byId("FragmentPanel").bindElement("/InputData('" + this.taskId + "')", {expand:"Antrag"});
  claimTask(this.taskId);
  var oMitarbeiter = sap.ui.getCore().byId("Mitarbeiter");
  oMitarbeiter.setEnabled(false);
  var oVorgesetzter = sap.ui.getCore().byId("Vorgesetzter");
  oVorgesetzter.setEnabled(false);
  var oStatus = sap.ui.getCore().byId("Status");
  oStatus.setEnabled(false);
  },
/**
* Similar to onAfterRendering, but this hook is invoked before the controller's View is re-rendered
* (NOT before the first rendering! onInit() is used for that one!).
* @memberOf demo.AntragM
*/
// onBeforeRendering: function() {
//
// },
/**
* Called when the View has been rendered (so its HTML is part of the document). Post-rendering manipulations of the HTML could be done here.
* This hook is the same one that SAPUI5 controls get after being rendered.
* @memberOf demo.AntragM
*/
// onAfterRendering: function() {
//
// },
/**
* Called when the Controller is destroyed. Use this one to free resources and finalize activities.
* @memberOf demo.AntragM
*/
// onExit: function() {
//
// }
  handleAbschliessenPress: function() {
  var outputData = {};
  var antrag = this.getView().getModel().getProperty("/InputData('" + this.taskId + "')/Antrag");
  outputData.Antrag = antrag;
  sap.ui.getCore().getModel().create("/OutputData", outputData, null, function(oEvent) {
  //Aufgabenfenster schliessen
  sap.m.MessageBox.show("Die Aufgabe wurde erfolgreich abgeschlossen. Sie können das Fenster schliessen.",
  sap.m.MessageBox.Icon.SUCCESS,
  "Aufgabe erfolgreich abgeschlossen");
  });
  }
});




Wish List

SAPUI5 is a great UI technology and (in my opinion) future-proof as it is based on modern and common web technologies. However, defining and layouting views is not comfortable, error-prone and no fun. A WYSIWYG-Editor for views similar to the web dynpro concept would be a huge additional value - I'm aware about the AppBuilder, but it is - in my opinion - only for rapid prototyping and is limited in functionallity and (as far as I know) it won't be developed further as well.

A disadvantage of developing client side javascript code is that there are no build-errors. It is very likely that you'll deploy non-valid code to the server. As App-Builder and River-RDE provide acceptable javascript code validation something in this quality would be desirable within NWDS as well.

There is already a plurality of non-consolidated development tools available for working with SAPUI5 (App Builder, NWDS, River RDE, some HANA-related Cloud tools). This is confusing. As SAP provides an eclipse-based IDE (NWDS) an consolidated IDE would be consequent - I'd like to develop processes and User Interfaces within the same tool.

Also, I noticed that the well known task header from web dynpro / VC tasks where you have some metadata-information as well as the process chart link is missing when using custom technologies as task uis.

I'd like a SAP provided SAPUI5 replacement for this

Other

I'd like to thank Andre Backofen (andre.backofen) for his uncomplicated support. Also, check out my collegue (patrick.wenger)'s Blog Posts about this issue and other SAPUI5 related stuff:

I hope you enjoyed my first Blog Post. Comment if you have any questions or remarks

Jan

7 Comments
Labels in this area