Background

There is always a need to store data within any Web or Mobile application for handling offline scenarios or for processing data when there is no network connectivity. I had done a small POC for a SAPUI5 based application and would like to share with you community members. You can post your comments or suggestions to add more value to this blog. 😛

Purpose

This blog illustrates the implementation of offline storage of data from an OData Service in a SAPUI5 Application designed for Web and Mobile.

IndexedDB & SAPUI5 JQuery Storage are the two common techniques used for the implementation.

Scope

We will be creating a simple SAPUI5 application integrating a SAP OData Service and try to store the retrieved data within the browser using IndexedDB & Local Storage.

OData Service

I have created an OData Service which will get records from the backend table. The structure of the collection which we will be using is as follows;

OData Service.png

   where, Enitity is “Employee”

                 EntitySet is “EmployeeCollection”

And, this entity will store details of an employee like ECODE, FIRSTNAME, LASTNAME, DEPTCODE and DEPTDESC.

We will now consume this service in our SAPUI5 application.


Creating View and OData Model

We will create a SAPUI5 application with a single view which will contain two tables, one to display data from OData Service & another to display data from offline storage.

SAPUI5 View.png

We will be storing the data retrieved from our OData service into our offline store & later display the same from the offline store into our view.

Let’s create a sap.m.Table with 5 columns (as we have 5 properties in our collection) in createContent function of our view.js.

var emplTbl = new sap.m.Table(“id_empTbl”, {

width : ‘600px’,

columns: [

           new sap.m.Column({

           header: new sap.m.Text({

           text: “Emp Code”

           })

               }),

           new sap.m.Column({

           header: new sap.m.Text({

           text: “First Name”

           })

               }),

           new sap.m.Column({

           header: new sap.m.Text({

           text: “Last Name”,

           })

               }),

           new sap.m.Column({

           header: new sap.m.Text({

           text: “Dept Code”

           })

               }),

           new sap.m.Column({

           header: new sap.m.Text({

           text: “Description”

           })

               }),

   ],

        

      items : {

      path: ‘/EmployeeCollection’,

      template: new sap.m.ColumnListItem({

      selected: true,

      type: “Active”,

      cells: [

                 new sap.m.Text({

                 text: “{ECODE}”

                     }),

                new sap.m.Text({

                text: “{FIRSTNAME}”

                     }),

                new sap.m.Text({

                 text: “{LASTNAME}”

                     }),

                new sap.m.Text({

                 text: “{DEPTCODE}”

                     }),

                new sap.m.Text({

                 text: “{DEPTDESC}”

                     })]

                  })

          }});

Let’s create another sap.m.Table to display data from offline store.

var emplOfflineTbl = new sap.m.Table(“emplOfflineTbl”, {

               width : ‘600px’,

               columns: [

                         new sap.m.Column({

                          header: new sap.m.Text({

                          text: “Emp Code”

                               })

                              }),

                         new sap.m.Column({

                          header: new sap.m.Text({

                          text: “First Name”

                               })

                              }),

                         new sap.m.Column({

                          header: new sap.m.Text({

                          text: “Last Name”,

                               })

                              }),

                         new sap.m.Column({

                          header: new sap.m.Text({

                          text:”Dept Code”

                               })

                              }),

                         new sap.m.Column({

                          header: new sap.m.Text({

                          text: “Description”

                               })

                              }),

                              ],

                    });

Let’s put the both the tables in a layout.

var oLayout = new sap.ui.layout.HorizontalLayout(“horizontalLayout”, {

               width: “100%”,

               content: [emplTbl,emplOfflineTbl]

     });

Two buttons at the top. The press event of the buttons refer to methods in controller.js, which we will be coding later in this section.

var bar = new sap.m.Bar(“bar”, {

          contentLeft : [new sap.m.Label(“label”, {

     text: “Displaying On-line data from OData Service”

               })],

          contentRight: [ new sap.m.Button(“storeOffline”,

                         {text: “Store data offline”,

          press: oController.writeToIDB}),

       new sap.m.Button(“showOffline”,

          {text: “Show offline data”,

                              press: oController.showOfflineData }),

]

});

Finally let’s return our content in a page.

          return new sap.m.Page({

               title: “Employee Records”,

               content: [bar,oLayout]

               });

Let’s create an OData Model to integrate our OData service and bind it to the UI control (first table).

We will modify the onInit function of our controller.js.

onInit: function() {

serviceUrl = “http://<host>:<port>/sap/opu/odata/sap/<external_service_name>”;

oModel = new sap.ui.model.odata.ODataModel(serviceUrl,false)

},

Bind the model to your first sap.m.Table. Modify the onBeforeRendering function in your controller.js.

onBeforeRendering: function() {

          sap.ui.getCore().byId(“id_empTbl”).setModel(oModel);

               },

Let’s run our project and see the output;


Output1.png

Records from OData Service are displayed in our view.

We will be storing these records in an IndexedDB within our browser.

IndexedDB

IndexedDB allows you to create a client-side persistent storage to store large amount of data within the browser.

It helps in storing the data in structured way thus enhancing the browser based app’s capability to not only store data for offline usage but also query the data more efficiently.

Step1: Let’s check if our browser supports IndexedDB.

if(window.indexedDB == null)

{

               console.error(“Offline store not supported!”);

               return null;

}

Step2: If supported, let’s create a database.

else{

               var createDBRequest= window.indexedDB.open(“MyOfflineDB”, 1);

     }

where, “MyOfflineDB” is the database name and “1” is the database version

Step3: Let’s add few events to our request.

     createDBRequest.onupgradeneeded = function(event){

                         var db = event.target.result;

                                           };

     createDBRequest.onsuccess = function(event){

                    oController.myDB = event.target.result; //oController – which we will define in onInit function

          };

     createDBRequest.onerror = function(oError)

     {

alert(“Something went wrong!”);

     };

Event onupgradeneeded will be called whenever a new database is created or an existing database with higher version is created.

Event onsucess will be called on successful opening of the database.

Event onerror will be called if anything goes wrong with our request.

Step4: Create an Object Store to hold the data.

In simple terms an Object Store is nothing but a Table with one or more fields. It helps in structuring our data.
An Object Store will hold the data in a key-value pair & will store the data according to keys in ascending order.

We will do this in our onupgradeneeded  event.

createDBRequest.onupgradeneeded = function(event){

var db = event.target.result;

var objectStore = db.createObjectStore(“EmployeeStore”, { keyPath: “ECODE” });

          objectStore.createIndex(“ECODE”, “ECODE”, { unique: true });

          objectStore.createIndex(“FIRSTNAME”, “FIRSTNAME”, { unique: false });

          objectStore.createIndex(“LASTNAME”, “LASTNAME”, { unique: false });

          objectStore.createIndex(“DEPTCODE”, “DEPTCODE”, { unique: false });

          objectStore.createIndex(“DEPTDESC”, “DEPTDESC”, { unique: false });

     };

So here I have created an Object Store which will hold the Properties of my Collection of my OData Service.

In my Object Store ECODE (Employee Code) is the key.

Step5: Once we have created our Database and Object Store to store our data from an OData Service, lets code to write the data to the Object Store.

Remember, we had created OData Model in our onInit function of controller.js.

We will get all the objects from that model.

var oObjects = oModel.oData;

Then we will loop at these objects and store one-by-one in the Object Store.

for (var key in oObjects) {

                    if (oObjects.hasOwnProperty(key)) {

var oObject = oObjects[key];

var oRecord = {ECODE: oObject.ECODE, FIRSTNAME: oObject.FIRSTNAME, LASTNAME: oObject.LASTNAME ,DEPTCODE: oObject.DEPTCODE, DEPTDESC: oObject.DEPTDESC  };

var oTransaction = oController.myDB.transaction([“EmployeeStore”], “readwrite”);

var oDataStore = oTransaction.objectStore(“EmployeeStore”);

                              oDataStore.add(oRecord);

                         }

Step6: Let’s retrieve the stored data

There are a couple of ways to retrieve the data in Object Store.

1. Using objectStore.get(“key”) method wherein we can fetch a single record using its key

2. Using a iterator – cusror, to iterate over multiple records

var objectStore = oController.myDB.transaction(“EmployeeStore”).objectStore(“EmployeeStore”);

var items = [];

          objectStore.openCursor().onsuccess = function(event) {

          var cursor = event.target.result;

          if (cursor) {

               items.push(cursor.value);

               cursor.continue();

        }

else {

                         alert(“Done Processing”);

                          // Logic to bind the obtained data in “items” array to your UI control

      }

Here, we will access our Object Store, iterate over records using cursor and store it in an array.

The IF condition checks whether there exists any further (next) record or not.

After processing the last record, the control goes into ELSE condition where you can play with the obtained data and your UI control.

Logic Implementation

Let’s organize our code in controller.js.

We will create our offline database while our application is being initialized.

Modify your onInit function in controller.js.

     onInit: function() {

          serviceUrl = “http://<host>:<port>/sap/opu/odata/sap/<external_service_name>“;

          oModel = new sap.ui.model.odata.ODataModel(serviceUrl,false)

               oController = this;

               this.prepareIDB();

           },

We are calling another method “prepareDB”.

prepareIDB: function(){

if(window.indexedDB == null)

{

               console.error(“Offline store not supported!”);

               return null;

}

else {

          var createDBRequest = window.indexedDB.open(“MyOfflineDB”, 1);

          createDBRequest.onupgradeneeded = function(event){

var db = event.target.result;

var objectStore = db.createObjectStore(“EmployeeStore”, { keyPath: “ECODE” });

                    objectStore.createIndex(“ECODE”, “ECODE”, { unique: true });

                    objectStore.createIndex(“FIRSTNAME”, “FIRSTNAME”, { unique: false });

                    objectStore.createIndex(“LASTNAME”, “LASTNAME”, { unique: false });

                    objectStore.createIndex(“DEPTCODE”, “DEPTCODE”, { unique: false });

                    objectStore.createIndex(“DEPTDESC”, “DEPTDESC”, { unique: false });

               };

          createDBRequest.onsuccess = function(event){

               oController.myDB = event.target.result;

          };

          createDBRequest.onerror = function(oError)

          {    

                    alert(“Something went wrong!”);

          };

           }

     },

Remember we have declared the press event (“press: oController.writeToIDB”) of our button to store data in offline store. Let’s have the method as below;

writeToIDB: function(){

var oObjects = oModel.oData;

for (var key in oObjects) {

               if (oObjects.hasOwnProperty(key)) {

var oObject = oObjects[key];

var oRecord = {ECODE: oObject.ECODE, FIRSTNAME: oObject.FIRSTNAME, LASTNAME: oObject.LASTNAME ,DEPTCODE: oObject.DEPTCODE, DEPTDESC: “My offline data”  };

// altering the last property value, just for display purpose

var oTransaction = oController.myDB.transaction([“EmployeeStore”], “readwrite”);

var oDataStore = oTransaction.objectStore(“EmployeeStore”);

                    oDataStore.add(oRecord);

               }

          alert(“Data stored in db for offline usage.”);

          },

Similarly, we will code another button’s press event method (press: oController.showOfflineData) to get the data from our offline store and display it in the second sap.m.Table at runtime.

showOfflineData: function(callback)

{

var objectStore = oController.myDB.transaction(“EmployeeStore”).objectStore(“EmployeeStore”);

var items = [];

          objectStore.openCursor().onsuccess = function(event) {

          var cursor = event.target.result;

          if (cursor) {

               items.push(cursor.value);

               cursor.continue();

               }

          else {

alert(“Done Processing”);

var oJSONModel = new sap.ui.model.json.JSONModel();

                    oJSONModel.setData({modelData: items});

                    sap.ui.getCore().byId(“emplOfflineTbl”).setModel(oJSONModel);

var oTemplate = new sap.m.ColumnListItem( 

{cells: [  

         new sap.m.Text({text : “{ECODE}”}),

         new sap.m.Text({text : “{FIRSTNAME}”}),

         new sap.m.Text({text : “{LASTNAME}”}),

         new sap.m.Text({text : “{DEPTCODE}”}),

         new sap.m.Text({text : “{DEPTDESC}”}),

         ] 

});

                    sap.ui.getCore().byId(“emplOfflineTbl”).bindItems(“/modelData”,oTemplate);

                }

};

      }

Here, we are retrieving the data from our “EmployeeStore” into “items” array. Then creating a JSONModel containing this data & binding it to our second sap.m.Table.

Test our app

Let’s run our SAPUI5 application and test our functionality.

First Run:

Output2.png

Check resources in your browser:


Output3.png

An IndexedDB “MyOfflineDB” with an Object Store “EmployeeStore” containing required fields is created.

Click on button “Store data offline” and check your IndexedDB:

You will see records being stored in the offline store.


Output4.png

Click on button “Show offline data” which will display the data from offline store into our view:


Output5.png


JQuery Storage API’s

Another simple way of storing data locally within the browser is by using SAPUI5 JQuery Storage.

There are 3 possible ways of storing your data using JQuery Storage API’s;

  1. jQuery.sap.storage.Type.global  – uses browser’s global storage
  2. jQuery.sap.storage.Type.local    – uses browser’s local storage
  3. jQuery.sap.storage.Type.session  – uses browser’s session storage

You may refer below link for more details on JQuery Storage API’s;

https://sapui5.hana.ondemand.com/sdk/#docs/api/symbols/jQuery.sap.storage.html

JQuery Local Storage Implementation

Step 1: Initialize JQuery Store

          jQuery.sap.require(“jquery.sap.storage”); 

Step 2: Define type

     oJQueryStorage = jQuery.sap.storage(jQuery.sap.storage.Type.local); 

Step 3: Create JQuery Store & use the “put” method to store the data from OData Model into the

JQuery Store

          oJQueryStorage.put(“myJQueryStorage”, oModel.oData); 

     where, oModel is our OData Model

Step 4: Use the “get” method to retrieve the data from your JQuery Store

     var offlineData = oJQueryStorage.get(“myJQueryStorage”);

Logic Implementation

Let’s integrate the JQuery Storage API’s in our SAPUI5 application.

We will create another button in our first view.

Output6.png

Also, we will create a similar sap.m.Table to display the records from JQuery Store. You may create this table inside the same view. In my case, I have created another view having the below table;

var emplOfflineTblJQuery = new sap.m.Table(“emplOfflineTblJQuery”, {

width : ‘600px’,

columns: [

          new sap.m.Column({

header: new sap.m.Text({

text: “Emp Code”

})

          }),

          new sap.m.Column({

header: new sap.m.Text({

text: “First Name”

})

          }),

          new sap.m.Column({

header: new sap.m.Text({

text: “Last Name”,

})

          }),

          new sap.m.Column({

header: new sap.m.Text({

text: “Dept Code”

})

          }),

          new sap.m.Column({

header: new sap.m.Text({

text: “Description”

})

          }),

          ],

});

Appropriately add the following piece of code in your first view.js (in the top UI bar).

new sap.m.Button(“showOfflineJQuery”,

{text: “Show offline data JQuery”,

press: oController.showOfflineDataJQuery})

We will write code for the press event (press: oController.showOfflineDataJQuery) of this button (“Show offline data JQuery”).

Modify your controller.js to add another function “showOfflineDataJQuery”.

showOfflineDataJQuery: function()

{

          jQuery.sap.require(“jquery.sap.storage”); 

oJQueryStorage = jQuery.sap.storage(jQuery.sap.storage.Type.local); 

          oJQueryStorage.put(“myJQueryStorage”, oModel.oData); 

var offlineData = oJQueryStorage.get(“myJQueryStorage”);

var items = [];

// looping over this offlineData to modify one of the property

// just for display purpose

for (var key in offlineData) {

if (offlineData.hasOwnProperty(key)) {

offlineData[key].DEPTDESC = “From JQuery Store”;

               items.push(offlineData[key]);

}

}

var oJSONModel = new sap.ui.model.json.JSONModel();

               oJSONModel.setData({modelData: items});

               sap.ui.getCore().byId(“emplOfflineTblJQuery”).setModel(oJSONModel);

var oTemplate = new sap.m.ColumnListItem( 

{cells: [  

         new sap.m.Text({text : “{ECODE}”}), 

         new sap.m.Text({text : “{FIRSTNAME}”}),

         new sap.m.Text({text : “{LASTNAME}”}),

         new sap.m.Text({text : “{DEPTCODE}”}),

         new sap.m.Text({text : “{DEPTDESC}”}),

         ]

});

               sap.ui.getCore().byId(“emplOfflineTblJQuery”).bindItems(“/modelData”,oTemplate);

               app.to(“id.JQuery”); // navigating to my second view

     },

     where,

          oModel is our OData Model

          myJQueryStorage is the name of our offline store

          offlineData contains the Employee records retrieved from offline store

          items is a JSON array consisting of Employee records retrieved from offline store


Test our app

Let’s run our SAPUI5 app to test our offline store created using JQuery API’s.

Click on “Show offline data JQuery” button;

Output7.png

You can view the table containing records retrieved from our local offline store created using JQuery API.

Output8.png

Also, you can inspect Resources to view the Offline Local Storage.


Output9.png


JQuery Storage v/s IndexedDB

Sr. No.

JQuery Storage

IndexedDB

01

Used for storing small amount of data

Used to store larger amounts of data

02

Simple to code & implement, supports synchronous API

Complex to implement but supports both synchronous & asynchronous API’s

03

Provides simple storage like key-value pair

Provides a structured database like storage

04

Used for storing objects in the form of serialized strings

We can design Object Stores to have multiple columns/ fields

05

Provides methods like GET, PUT, REMOVE etc. for data operations

Use of Transactions to add, retrieve & remove data having success & error callbacks for exception handling

06

Does not provide special features like indexing/ key generator

Provides features like indexing and key generation

07

HTML5 storage – mostly supported by all browsers

May not be supported some browser (old versions)

08

Especially used for storing simple session specific values

Especially used for storing large number of records for building offline solutions

To report this post you need to login first.

16 Comments

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

    1. Dharmaraj Patil Post author

      Thanks Buzek.

      SAPUI5’s own jquery.sap.storage is simple to implement as you can directly store data from the model & its easy to retrieve. While indexedDB can provide developers the option to created a structured store, key generation, indexing and asynchronous processing.

      Depending upon how complex and robust offline capability is to be developed, one can choose either of them.

      Regards

      (0) 
    1. Dharmaraj Patil Post author

      Yes Vishnu. There are few API’s which allow creation of file system on local hard drive. And doing it from a web based app is possible but tricky. Accessing your drives storage from browser has to be secured & controlled.

      I have seen web based mail clients storing email attachments locally in file systems.

      As I have not worked with such API’s I may not be in a position to give pro’s & cons of the same.

      (0) 
  1. Deepak Anumula

    Hi Dharmaraj,

    Thanks for great info,really very helpful.

    I have one small query,

    I created table with 3 input fields and 2 buttons offline and Addrow.

    for example

    i clicked on offline button for the first time then the values are storing in localdb,

    but when i clicked on addrow then i will get the 2 rows in my table and click on offline then the values of second row are not storing in the local db.

    Could u please help out of this.

    Thanks,

    Deepak Raj.

    (0) 

Leave a Reply