Skip to Content

Going Offline With Kapsel Now

In this article I’ll show you how to build a Kapsel mobile app, which uses an OData delta query service, some Kapsel plugins, the SMP Cloud, an offline database and SAPUI5. This allows you to create cross-platform, offline Apps. We will showcase this using a Mac, so on Windows there will be slightly different commands used.  Also this is not a beginner tutorial. You need to be familiar with all the mentioned resources and blogs referred in the text below.

Prerequisites on your local machine

To follow this blog properly, Cordova has to be installed on your system.  Check this blog for more information: http://scn.sap.com/docs/DOC-49592.

Here the whole end to end process is described that starts with registering the OData Service in SMP and ends with the consumption via the Kapsel framework.

You also need the SMP 3.0 SDK. Follow the installation instructions in the downloadable files. You can download the SDK from https://service.sap.com/.

For the offline database part, we use PouchDB as a framework, because it handles all the database stuff internally and supports every modern browser. You can simply download the minified version from http://pouchdb.com/.

Since we develop an offline application, we need a static SAPUI5 library. You can download it from: http://sap.github.io/openui5/index.html.

You don’t exactly need everything from the static library. In our case, we only need the resources folder and within it only sap/ui and sap/m. If you want, you can also delete all the *-dbg.js files.

Of course, you can also use any other UI Framework, such as jQuery Mobile if you want. If so, just skip the UI part.

In order to get your project in shape you first have to create a Cordova project on your local computer, add the desired target platforms (iOS and/or Android) to your project and finally add all the Kapsel plugins to that project. In this example we use two plugins: Logon, which handles the complete onboarding process and EncryptedStorage that gives us a way to securely store data on the device. These steps are also explained in the SCN blog, mentioned above, so please follow them accordingly before advancing with the steps described here. If anything goes wrong, please double check your proxy settings, both http and https for npm and Git.

Tricks for Android

If you wish to develop an android application, you need to configure some things. First make sure you have installed a JDK version on your local machine. Afterwards create a new environment variable JAVA_HOME and insert the root folder of your JDK installation path. You also need ant for the build process. To call the ant command from the command line, we have to add the <ant>/bin folder to our Path variable. Also <android_bundle>/sdk/tools and <android_bundle>/sdk/platform-tools have to be added to your Path variable. At any time, you should be able to execute the android, adb and ant command from the command line.

Prerequisites on your server side

In addition you need an SAP Mobile Platform installation, either SMP 3.0 or SMP Cloud would work. If you haven’t got any SMP server, you can get a free trial cloud account here https://account.hanatrial.ondemand.com.

Please navigate to your Administration portal of SMP Cloud and create a new Application. There you maintain the backend endpoint, which should be your OData delta query service. If your service needs any type of authentication, please also maintain it by creating a security profile.

These steps are described here: http://scn.sap.com/community/developer-center/mobility-platform/blog/2013/11/11/how-to-use-the-smp-cloud-with-copy-paste

Last but not least, you need an OData delta query service, that is published by SAP NetWeaver Gateway, which supports delta queries. In this document, we will use a service, which gives us products. The development of this service has been described in the SCN document How to Implement Basic Delta Query Support in SAP NetWeaver Gateway. How to register an OData service in SMP is desribed in the aforementioned documetn Getting Started with Kapsel - Part 1.

You might also want to check out the following document How To Enable Delta Queries using Syclo Exchange Framework and SAP NetWeaver Gateway that explains how delta queries can leverage the Syclo Exchange Framework.

Delta query means "give me all services that were created/changed/deleted since I last asked". In other words, the delta query protocol defines a pull-model protocol for clients to obtain changes from a store. Clients can poll the store for changes periodically or as required, or capable stores can provide change notifications to alert the client when changes are available to be pulled.

Prerequisites Summary

On your local machine you should have installed:

  • SAP Mobile SDK 3.0
  • OpenUI5 JS Library (or jQueryMobile if desired)
  • Apache Cordova (which depends on node.js)
  • PouchDB JS Library

On your SAP Mobile Platform Server you need to configure:

  • A valid Application Configuration that includes a Security Configuration
  • A valid OData Service published via SAP NetWeaver Gateway that publishes the SAP backend data that supports DeltaQueries

Development

Notes

In the code, db is denoted as an object to a PouchDB, which has to be created first of all. ApplicationContext is denoted as the context, which you get either from the encrypted storage if the user and the device have already been registered or from the success callback function of sap.Logon.init. onError is denoted as a function, which will be called every time something fails.

Development Lifecycle

The Cordova structure is defined as follows:
You will change, delete and add files only in your www folder. If you’re done, you execute this command to update the existing platforms (make sure you are in the project folder):

sudo cordova -d prepare ios

Please take your time to examine the folder structure that this command creates. It will help to understand the development lifecycle.

You can simply append all platforms, which you’ve added in your project behind ios with a space. Essentially, all the prepare statement does is to update the www folder and all plugins inside of the specified platform.

If you haven’t opened your native generated application, which is under <project>/platforms/<platform>, in your IDE, please do it now. XCode automatically detects changes to the project, so you can just execute it via Apple + R or by pressing the build button in the upper left corner. Though, in Eclipse I would make a refresh on the project, just to be sure, and afterwards execute the project.

Dependencies and framework(s)

In the <head> tag of the index.html – our App starting point, we need to include our frameworks, such as cordova.js, pouchdb.js and SAPUI5 as long as you use it. Make sure you correct the paths regarding your environment.

<meta http-equiv="X-UA-Compatible" content="IE=edge" />

<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>

<script type="text/javascript" charset="utf-8" src="cordova.js"></script>

<script src="resources/sap-ui-core.js" id="sap-ui-bootstrap" data-sap-ui-libs="sap.m" data-sap-ui-theme="sap_bluecrystal"></script>

<script type="text/javascript" src="js/pouchdb-1.1.0.min.js"></script>

UI

Our HTML body looks like this, which is default for SAPUI5 applications:

<body class="sapUiBody">

    <div id="content"></div>

</body>

This is the code, we will use for our UI. It’s just a simple list to show our data and two buttons at the top of the app, one for refreshing and the other one for resetting the products.

var customHeader = new sap.m.Bar("mainShellBar", {

    contentMiddle: [ new sap.m.Label("mainShellBarTitleLabel", { text: "ODataDelta" }) ],

    contentRight: [ new sap.m.Button({ text: "Reset", tap: resetButtonClicked }), new sap.m.Button({ text: "Refresh", tap: refreshButtonClicked }) ]

});

var oProductList = new sap.m.List("productList", { headerText: "Products:" });

var oProductsPage = new sap.m.Page("productsPage", {

    content: oProductList,

    customHeader: customHeader

});

var oApp = new sap.m.App("ODataDelta", { initialPage: "productsPage" });

oApp.addPage(oProductsPage);

In the refreshButtonClicked function, we’ll just send a GET request against our OData delta query service. For more information about this, check the segment at the end of this blog.

The resetButtonClicked function is pretty easy and looks like this:

var resetButtonClicked = function () {

PouchDB.destroy(“<table_name>”, function (err, info) { });

    db = new PouchDB(“<table_name>”);

localStorage.deltatoken = "";

oProductList.destroyItems();

};

It’ll just delete the local stored database table and initialize a new one. Afterwards it deletes the locally stored deltatoken and destroys all the items in the list.

To add a list item you can use this snippet:

oProductList.addItem(

    new sap.m.StandardListItem(“yourId”, {

        title: “yourTitle”,

        description: “yourDescription”,

        icon: “yourIconUrl”

    })

);

If we tried to run this code, it would not show anything, because we haven’t placed the app on the body yet. We’ll make this at a later point.

Deviceready event

In Cordova there is a deviceready event, where a function of yours can be triggered after Cordova is ready to execute your code. Within this function, we have to check if the user and the device have already been registered at the SMP or not. If so, we can show them the content of our app. If not, we’ll use the Kapsel Logon plugin provided with the SMP 3.0 SDK to pop up a login screen to let the user enter his credentials and register at the SMP. Once the registration is successfully done, we’ll get an applicationContext, which is just a JSON object containing some very important data, such as the username, password, host, applicationConnectionId and so on. We have to somehow save this context locally on the device. Luckily there is an EncryptedStorage Kapsel plugin, which is also provided with the SMP 3.0 SDK. All these steps are done in the next snippet:

document.addEventListener("deviceready", function() {

setTimeout(function() { // need to do this, otherwise the Kapsel plugins will fail, because they are not loaded yet

        var encryptedStorage = new sap.EncryptedStorage("<storeName>", "<storePassword>");

encryptedStorage.getItem("applicationContext", function(value) {

            if (value == null) { // First time the app is started

sap.Logon.init(function (appContext) {

applicationContext = appContext;

encryptedStorage.setItem("applicationContext", JSON.stringify(applicationContext), function() {

alert("Did successfully register");

oApp.placeAt("content");

                    }, onError);

                }, onError, “<yourAppIdInSmp>, { "serverHost": "yourServerHost ", "serverPort": "443", "https": "true", "user": "yourUser", "password": "yourPassword" });

            } else { // already registered at the SMP

                applicationContext = JSON.parse(value);

oApp.placeAt("content");

db.allDocs({ include_docs: true }, function (err, response) {

var products = response.rows;

});

            }

        }, onError);

    }, 3000);

}, false);

You may ask yourself, why is there a timeout used after the deviceready event? It is because the Kapsel plugins haven’t been initialized and loaded properly at this point. To fix this, we have to wait. Three seconds is just a wild guess, which works for me, although you may have to change it. This behavior will change in the future to ensure better usage of the plugins.

Login screen

If you want to change the login form, shown by sap.Logon.init, provided by the Logon Kapsel plugin, you can simply edit the fields array of the SCR_REGISTRATION object in this file:  <path>/<project>/plugins/com.sap.mp.cordova.plugins.logon/www/common/modules/StaticScreens.js

If your application does not need a farmId, you can simply comment that object out.

Afterwards, you have to prepare your project in order to update the plugins in the native applications. FarmId is used for SMP versions prior to SMP 3.0. The Kapsel Logon plugin does support SUP2.2 and higher and also direct SAP NetWeaver Gateway connections.

This what the login form can look like on iOS:

PouchDB operations

In this section, I’ll cover some basic API calls using the PouchDB.

Creating an offline database table

var db = new PouchDB(“<table_name>”);

Destroying an offline database table

PouchDB.destroy(“<table_name>”, function (err, info) { });

Note: After executing this command, you have to recreate your database table

Adding a record

db.put({ _id: “some unique id”, field: “value”, “key”: “test”, function (err, response) { });

Updating a record

db.get(“<id>”, function (err, resp) {

    yourUpdatedObject._rev = resp._rev;

    db.put(yourUpdatedObject, function (err, response) { });

});

Delete a record

db.get(“<id>”, function (err, doc) {

    db.remove(doc, function (err, response) { });

});

Select all

db.allDocs({ include_docs: true }, function (err, response) {

    var products = response.rows;

});

The complete API can be found here: http://pouchdb.com/api.html

Requests

For every request against the SMP we have to set two request headers (X-SMP-APPCID and X-SUP-APPCID) with the appcid (application-conntection-id) we got from the registration process. If your OData delta query service needs Basic Authentication, you also have to set the corresponding request header. The request can be sent via the Ajax function, provided by jQuery.

var refreshButtonClicked = function () {

    var deltatoken = localStorage.deltatoken || "";

    $.ajax({

        type: "GET",

        url: applicationContext.applicationEndpointURL + "<path>/<collection>" + deltatoken,

        headers: {

"X-SMP-APPCID": applicationContext.applicationConnectionId,

            "X-SUP-APPCID": applicationContext.applicationConnectionId,

"Authorization": "Basic " + btoa(applicationContext.registrationContext.user + ":" + applicationContext.registrationContext.password)

        },

        dataType: "xml",

        success: function (data) {

localStorage.deltatoken = $(data).find('link[rel="delta"]')[0].getAttribute("href").replace("<collection>", "");

        },

        error: function (xhr, ajaxOptions, thrownError) {

            if (xhr.status == 400) { // invalid deltatoken

alert("Invalid deltatoken. Will refetch all data");

resetButtonClicked();

refreshButtonClicked();

            } else {

alert("Refresh failed. See logs for more information");

console.log(xhr.status); console.log(xhr.responseText); console.log(JSON.stringify(ajaxOptions)); console.log(JSON.stringify(thrownError));

            }

        }

    });

};

First of all, we try to get the deltatoken if it has been set. Since we want to send a GET request to an OData delta query service, which will give us an xml string, we have to specify it together with the required request headers mentioned above.

In the success callback function, we will just log the data and write the new deltatoken to the localStorage. In our code snippet the XML data is written to the console. In a real scenario, you would do something with the data variable, which contains the data from your service. For example showing it as list as shown in the screen shot below.

In the failure callback function, we will check if the deltatoken is valid. (Depending on the implementation that is used to determine the delta query results a deltatoken might become invalid as described in section Clean Up Report in How to Implement Basic Delta Query Support in SAP NetWeaver Gateway)

If it is invalid (HTTP 400), we just simulate as if the reset- and refreshButton has been clicked. If another error (HTTP <> 400) has occured, we just print out the error(s).

If you test this snippet in a browser, for instance Google Chrome, you need to disable the same-origin-policy.

End result

This is how your app can look like:

Tips & Tricks

Syntax errors

To see some kind of syntax errors, you can use this handy function. Make sure it’s at the beginning of your JavaScript code, to detect all errors:

window.onerror = function (msg, url, line) {

    var idx = url.lastIndexOf("/");

    alert("An error occurred in " + (idx > -1 ? url.substring(idx + 1) : "unkown") + " (at line # " + line + "): " + msg);

    return false;

};

Debugging

If you use the iPhone simulator, you can turn the “Web Inspector” on via:

Settings -> Safari -> Advanced -> Web Inspector. Normally it should be on by default. Afterwards start your application and open Safari on your Mac.

Go to preferences -> Advanced and check “Show Develop menu in menu bar”.

Afterwards go to Develop -> iPhone Simulator -> <project> -> Index.html.

Now you can go through your scripts and use the console to “debug”.

Conclusion

It was relatively straightforward to create an offline, cross platform application.  The delta query greatly enhances performance because you don’t have to load all the data every time again and again. But even without a delta service you could have created this offline application, it would just not be so responsive.

Tags:
Former Member