In this blog, I would like to share my experience on how to develop a simple SAP mobile app using Sencha Touch with view, update, create and delete operations.

The objective of this app is to be able to view, update, create and delete a record in TravelagencyCollection-RMTSAMPLEFLIGHT that is published by OData service SAP Netweaver Gateway Service Consumption System.

Required Components

We would need the following components in order to build the app:

  1. Sencha Touch framework from http://www.sencha.com
    Download this framework. We will write the code using this framework.
  2. Sencha Touch OData Connector for SAP
    Download the connector from https://market.sencha.com/extensions/sencha-touch-odata-connector-for-sap to connect  to SAP OData available services. File we need is OData.js.
  3. Apache
    We will host the code on Apache web server and we will configure the reverse proxy to resolve the “same-origin policy” issue.
    Download Apache with SSL enabled  from http://httpd.apache.org/download.cgi
  4. Get an account from SAP Netweaver Gateway Demo system
    Register an account from https://supsignformssapicl.hana.ondemand.com/SUPSignForms

        We need this account to be able to use the sample service. In this case is RMTSAMPLEFLIGHT.

Configure Reverse Proxy

The first step that you need to do after installing  Apache web-server is to configure reverse proxy. I am not going into detail on how to install the Apache web server. In my configuration, I am using the Apache with SSL enabled.

Open your httpd.conf under Apache conf folder and uncomment the following lines:


LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule headers_module modules/mod_headers.so
LoadModule ssl_module modules/mod_ssl.so






































































Also add the following lines:


Header set Access-Control-Allow-Origin "*"
Header add Access-Control-Allow-Headers "Content-Type"
Header add Access-Control-Allow-Headers "X-Requested-With"
SSLProxyEngine "On"
RequestHeader set Front-End-Https "On"
ProxyPass /sapgw/ https://sapes1.sapdevcenter.com/
ProxyPassReverse /sapgw/ https://sapes1.sapdevcenter.com







































































In the URL connection string, we need to add the /sapgw/ to enable the reverse proxy, for example:

http://ASPSGPLR81BC0G/sapgw/sap/opu/odata/IWFND/RMTSAMPLEFLIGHT/TravelagencyCollection

Replace ASPSGPLR81BC0G with your computer name.

Any request to http://ASPSGPLR81BC0G/sapgw, will be directed by Apache to https://sapes1.sapdevcenter.com/

Let’s open this link from Chrome browser and you will get the result:

/wp-content/uploads/2014/05/ss1_444102.jpg


Page Structure


We will build three simple pages as follow:

  • Travel Agency List.
    On this page, user can view the list of travel agency with a button link to the Detail page.

/wp-content/uploads/2014/05/ss2_444435.jpg

  • Detail.
    On the Detail page, user can update or delete the record. They cannot update the Agency No field.

/wp-content/uploads/2014/05/ss3_444436.jpg


  • Add Travel Agency.
    On this page, user can add the new record. The Agency No is a required field.

/wp-content/uploads/2014/05/ss4_444442.jpg

Code Structure

As you can see in the below structure, we will build the MVC structure. You need to copy the OData.js (from Sencha Touch OData Connector from SAP) , sencha-touch-all.js and resources (from Sencha Touch) to the root folder, TravelAgency.

/wp-content/uploads/2014/05/ss5_444443.jpg

Let’s walk through the important files,

    • View: app.js

      We register the controller, model, store and views in app.js.
      
      controllers: ["TravelAgency"],
      models: ["TravelAgency"],
      stores: ["TravelAgency"],
      views: ["Viewport",
      "MainPanel",
      "TravelAgencyListContainer",
      "TravelAgencyEditor"
      "AddTravelAgentContainer",
      "AddTravelAgentMenu"
      ],
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      Page Corresponding View
      Travel Agency List TravelAgencyList
      Detail Page TravelAgencyEditor
      Add Travel Agency AddTravelAgentMenu
    • Model  and Store: TravelAgency.js
      In the model and store TravelAgency.js, we define all the required fields and URL connection in the proxy.
      
      fields: [
      { name: "agencynum" },
      { name: "NAME" },
      { name: "STREET" },
      { name: "POSTBOX" },
      { name: "POSTCODE" },
      { name: "CITY" },
      { name: "COUNTRY" },
      { name: "REGION" },
      { name: "TELEPHONE" },
      { name: "URL" },
      { name: "LANGU" },
      { name: "CURRENCY" },
      { name: "mimeType" }
      ],
      proxy: {
      type: 'odata',
      enablePagingParams: false,
      withCredentials: true,
      username: 'P1940517116',
      password: 'Password',
      url: "//ASPSGPLR81BC0G/sapgw/sap/opu/odata/IWFND/RMTSAMPLEFLIGHT/TravelagencyCollection",
      }
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    • View: TravelAgencyList.js
      To display out the Agency No (agencynum) and Agency Name (NAME) based on the data obtained from store TravelAgency and put a button link to the Detail page, onItemDisclosure

      
      config: {
      store: "TravelAgency",
      itemTpl: [
      '<div>',
      '<div>Agency No: {agencynum}</div>',
      '<div>{NAME}</div>',
      '</div>',
      ],
      onItemDisclosure: function(record,btn,index) {
      this.fireEvent("editTravelAgencyCommand", record);
      }
      },
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    • View: TravelAgencyEditor.js
      It will display Name, Street, Post Box, Post Code, City, Country, Region, Telephone, URL, Language and Currency based on the given agencynum.  We also put the Save (onSaveTap) and Delete (onDeleteTap) functions on this view.
      
      items: [
      { name: "NAME", label: "Name" },
      { name: "STREET", label: "Street" },
      { name: "POSTBOX", label: "Post Box" },
      { name: "POSTCODE", label: "Post Code" },
      { name: "CITY", label: "City" },
      { name: "COUNTRY", label: "Country" },
      { name: "REGION", label: "Region" },
      { name: "TELEPHONE", label: "Telephone" },
      { name: "URL", label: "URL" },
      { name: "LANGU", label: "Language" },
      { name: "CURRENCY", label: "Currency" }
      ],
      onSaveTap: function(button,e,options) {
      this.fireEvent("saveCommand", this.getRecord(), button);
      },
      onDeleteTap: function(button,e,options) {
      this.fireEvent("deleteCommand", this.getRecord(), button);
      ),
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    • View: AddTravelAgentMenu.js

      We populate the text fields for user to enter the Agency No, Name, Street, Post Box, Post Code, City, Country, Region, Telephone, Language, Currency, URL and add the Add (onFormAdd) function.

      
      items: [
      {
      xtype: 'textfield',
      label: 'Agency No',
      labelWrap: true,
      labelWidth: '30%',
      name: 'agencynum',
      placeHolder: 'Enter Agency No',
      },
      {
      xtype: 'textfield',
      label: 'Name',
      labelWrap: true,
      labelWidth: '30%',
      name: 'NAME',
      placeHolder: 'Enter Name'
      },
      {
      xtype: 'textfield',
      label: 'Street',
      labelWidth: '30%',
      name: 'STREET',
      placeHolder: 'Enter Street'
      },
      {
      xtype: 'textfield',
      label: 'Post Box',
      labelWidth: '30%',
      name: 'POSTBOX',
      placeHolder: 'Enter Post Box'
      },
      {
      xtype: 'textfield',
      label: 'Post Code',
      labelWidth: '30%',
      name: 'POSTCODE',
      placeHolder: 'Enter Post Code'
      },
      {
      xtype: 'textfield',
      label: 'City',
      labelWidth: '30%',
      name: 'CITY',
      placeHolder: 'Enter City'
      },
      {
      xtype: 'textfield',
      label: 'Country',
      labelWidth: '30%',
      name: 'COUNTRY',
      placeHolder: 'Enter Country'
      },
      {
      xtype: 'textfield',
      label: 'Region',
      labelWidth: '30%',
      name: 'REGION',
      placeHolder: 'Enter Region'
      },
      {
      xtype: 'textfield',
      label: 'Telephone',
      labelWidth: '30%',
      name: 'TELEPHONE',
      placeHolder: 'Enter Telephone'
      },
      {
      xtype: 'textfield',
      label: 'Language',
      labelWidth: '30%',
      name: 'LANGU',
      placeHolder: 'Enter Language'
      },
      {
      xtype: 'textfield',
      label: 'Currency',
      labelWidth: '30%',
      name: 'CURRENCY',
      placeHolder: 'Enter Currency'
      },
      {
      xtype: 'textfield',
      label: 'URL',
      labelWidth: '30%',
      name: 'URL',
      placeHolder: 'Enter URL'
      },
      ],
      onFormAdd: function(button,e,options){
      var formObj = button.up('addtravelagentmenu');
      var formData = formObj.getValues();
      this.fireEvent("addCommand", this.getRecord(), button);
      }
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      

    • Controller: TravelAgency.js

      On controller, we define the method for save, delete and update record.

      Update Method
      We need to specify an id field in order to update the record based on agencynum.

      
      onSaveButton: function(record, button) {
      travel = Ext.create('TravelAgency.model.TravelAgency', {
      id : "//ASPSGPLR81BC0G/sapgw/sap/opu/odata/IWFND/RMTSAMPLEFLIGHT/TravelagencyCollection('" +
      agencynum: record.get('agencynum'),
      });
      var formObj = button.up('travelagencyeditor');
      var formData = formObj.getValues();
      travel.set('agencynum', formData.agencynum);
      travel.set('NAME', formData.NAME);
      travel.set('STREET', formData.STREET);
      travel.set('POSTBOX', formData.POSTBOX);
      travel.set('POSTCODE', formData.POSTCODE);
      travel.set('CITY', formData.CITY);
      travel.set('COUNTRY', formData.COUNTRY);
      travel.set('REGION', formData.REGION);
      travel.set('TELEPHONE', formData.TELEPHONE);
      travel.set('URL', formData.URL);
      travel.set('LANGU', formData.LANGU);
      travel.set('CURRENCY', formData.CURRENCY);
      travel.save(function (record, operation) {
      console.log(record);
      console.log(operation);
      if (operation.getError()==null) {
      console.log('record udated. Id:' + record.get('agencynum'));
      Ext.Msg.alert('Status', "Record updated");
      } else {
      console.log('Create record failed');
      Ext.Msg.alert('Status', operation.getError());
      }
      var store = Ext.getStore("TravelAgency");
      store.loadPage(1);
      });
      },
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      

      Delete Method
      Similar to Update method, we need to specify an ID (agencynum) to delete the record.

      
      onDeleteButton: function(record, button) {
      mp = this.getMainPanel();
      travel = Ext.create('TravelAgency.model.TravelAgency', {
      id : "//ASPSGPLR81BC0G/sapgw/sap/opu/odata/IWFND/RMTSAMPLEFLIGHT/TravelagencyCollection('"
      + record.get('agencynum') + "')",
      agencynum: record.get('agencynum'),
      });
      var formObj = button.up('travelagencyeditor');
      var formData = formObj.getValues();
      travel.erase(function (record, operation) {
      console.log(record);
      console.log(operation);
      if (operation.getError()==null) {
      console.log('record deleted');
      Ext.Msg.alert('Status', 'Record deleted successfully.');
      } else {
      console.log('Delete record failed');
      Ext.Msg.alert('Status', 'Delete record failed.');
      }
      var store = Ext.getStore("TravelAgency");
      store.loadPage(1);
      Ext.Viewport.animateActiveItem(mp, { type: "slide", direction: "left" });
      });
      }
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      

      Create Method
      For Create method, we need to ensure the agencynum is not empty.

      
      onAddButton: function(record, button) {
      var formObj = button.up('addtravelagentmenu');
      var formData = formObj.getValues();
      travel = Ext.create('TravelAgency.model.TravelAgency', {
      agencynum: formData.agencynum,
      NAME: formData.NAME,
      STREET:  formData.STREET,
      POSTBOX:  formData.POSTBOX,
      POSTCODE:  formData.POSTCODE,
      CITY: formData.CITY,
      COUNTRY: formData.COUNTRY,
      REGION: formData.REGION,
      TELEPHONE: formData.TELEPHONE,
      URL: formData.URL,
      LANGU:  formData.LANGU,
      CURRENCY:  formData.CURRENCY
      });
      if (formData.agencynum == "") {
      Ext.Msg.alert('ERROR', 'Agency No could not be empty');
      } else {
      travel.set('agencynum', formData.agencynum);
      travel.set('NAME', formData.NAME);
      travel.set('STREET', formData.STREET);
      travel.set('POSTBOX', formData.POSTBOX);
      travel.set('POSTCODE', formData.POSTCODE);
      travel.set('CITY', formData.CITY);
      travel.set('COUNTRY', formData.COUNTRY);
      travel.set('REGION', formData.REGION);
      travel.set('TELEPHONE', formData.TELEPHONE);
      travel.set('URL', formData.URL);
      travel.set('LANGU', formData.LANGU);
      travel.set('CURRENCY', formData.CURRENCY);
      travel.save(function (record, operation) {
      console.log(record);
      console.log(operation);
      if (operation.wasSuccessful()) {
      Ext.Msg.alert('Status', 'Record added successfully. Record ID: ' + record.get('agencynum'));
      console.log('record created. Id:' + record.get('agencynum'));
      formObj.setValues({
      agencynum: '',
      NAME: '',
      STREET: '',
      POSTBOX: '',
      POSTCODE: '',
      CITY: '',
      COUNTRY: '',
      REGION: '',
      TELEPHONE: '',
      URL: '',
      LANGU: '',
      CURRENCY: ''
      });
      } else {
      console.log('Create record failed');
      }
      var store = Ext.getStore("TravelAgency");
      store.loadPage(1);
      });
      }
      },
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      


After you complete writing all the codes, you can launch the app from Chrome browse and it will give you the Travel Agency List page:

http://<your-apache-web-server>/TravelAgency/index.html

Some screenshots of edit, update, delete operations in action:

/wp-content/uploads/2014/05/ss6_444595.jpg

/wp-content/uploads/2014/05/ss7_444665.jpg


/wp-content/uploads/2014/05/ss8_444666.jpg

The complete source code can be found in GitHub: https://github.com/ferrygun/TravelAgency

Thanks for reading my blog, feel free to drop any comment/question.

To report this post you need to login first.

6 Comments

You must be Logged on to comment or reply to a post.

  1. Ranjith Lingala

    Hi Ferry,


    Apache
    We will host the code on Apache web server and we will configure the reverse proxy to resolve the “same-origin policy” issue.
    Download Apache with SSL enabled  from http://httpd.apache.org/download.cgi

    By above point for Apache configuration what does mean “Apache with SSL Enabled” and also provided does not contain such type of source. Can you please elaborate some deeeep.

    Regards,

    Ranjith

    (0) 
    1. Ferry Gunawan Post author

      My SAP Netweaver Gateway is in https mode, so that’s why I needed Apache with SSL-enabled. In the httpd.conf, I need to uncomment “LoadModule ssl_module modules/mod_ssl.so” and add “SSLProxyEngine On”. Hope it helps.

      (0) 
  2. Nitesh Jain

    Has anyone tried consuming deep entity Gateway service in Sencha Touch for POST/PUT methods?

    If so kindly share the code..

    regards

    Nitesh

    (0) 
    1. Michael Appleby

      Hi Nitesh,

      Please create a new Discussion marked as a Question.  The Comments section of a Blog (or Document) is not the right vehicle for asking questions as the results are not easily searchable.  Once your issue is solved, a Discussion with the solution (and marked with Correct Answer) makes the results visible to others experiencing a similar problem.  If a blog or document is related, put in a link.  Read the Getting Started documents (link at the top right) including the Rules of Engagement. 

      NOTE: Getting the link is easy enough for both the author and Blog.  Simply MouseOver the item, Right Click, and select Copy Shortcut.  Paste it into your Discussion.  You can also click on the url after pasting.  Click on the A to expand the options and select T (on the right) to Auto-Title the url.

      Thanks, Mike (Moderator)

      SAP Technology RIG

      (0) 

Leave a Reply