Skip to Content
SAPUI5

Going offline with SAP UI5

Tags:

Why care about offline?

The HTML5 specification makes it possible for developers to create web applications that run even without an internet connection. As amazing as that might sound at first, is it actually even relevant in today's modern "always-online" world? Probably yes.

Most business transactions cannot practically be initiated without a connection to a backend system. These are transactions that depend on data that can change at any time, and registering them with the backend system as soon as they are executed is crucial. However, for at least a handful of specific scenarios, offline applications can prove to be meaningful. While it is true that even Antarctica has internet access, there are still other remote places on Earth - especially in developing nations - where though there can be business, an internet connection is often either unavailable or unreliably slow. If you want to tap such opportunities, knowing how HTML5 makes offline applications a possibility is a must.

To make a complete offline application, you will need to befriend two new browser features that HTML5 standardizes.

  1. Caching web pages for offline viewing : Browsers can now be instructed to download portions of entire websites (or even entire websites!) and cache them on your end user's computer. When they try to access your website without an internet connection, these cached pages are rendered on their screen as if they were returned by your server.
  2. Offline local data storage : Till now one of the most widespread and truly cross-browser way of persisting data on the end user's computer, was by using cookies. There are also several restrictions on the type and size of data that you can store using cookies. HTML5 standardizes (or 'is in the process of standardizing' to be more precise) something called "Indexed DB". Using Javascript DOM APIs, you can create a NoSQL database with object stores to store Javascript objects in your end user's system. You can even create indices on these object stores and use cursors to iterate over sets of objects.

Caching and local data storage are not only useful for offline applications; they can even be used to boost the performance of online applications. Data for things like frequently used static F4 value helps, for example, could be cached on the client's system to reduce the number of roundtrips. Also, while we only create a desktop application in this example, resources can be cached by modern mobile browsers as well. So let's get started!

Demo application

What are we making?

In this document we'll learn how to create a simple UI5 application that contains just a couple of fields and a 'Submit' button. If an internet connection is available, this data is pushed to a backend service. If an internet connection is not available, the data is stored locally - till an internet connection becomes available.

What are we learning?

To achieve this, we'll need to get a grasp of the following topics :

  1. Instructing the browser to cache resources for offline access
  2. Checking if we have a network connection
  3. Creating and using a local database

Building the UI

Certainly not an impressive UI, I agree, but this is not what we're focussing on today. Let's just quickly have a glance at the createContent() method and move on to the more exciting stuff. onSubmit() is a method that we'll create later in the view's controller to handle the press event.

Checking for network connectivity

There are several ways to check if a network connection is available. The most common technique is to check the navigator.onLine property. But as well-informed UI developers, it is best that we stay away from this property (for now).

In Chrome and Safari, if the browser is not able to connect to a local area network (LAN) or a router, it is offline; all other conditions return true. So while you can assume that the browser is offline when it returns a false value, you cannot assume that a true value necessarily means that the browser can access the internet. You could be getting false positives, such as in cases where the computer is running a virtualization software that has virtual ethernet adapters that are always "connected." - Mozilla Developer Network Web API Reference

So how else do we check for connectivity? While there might be better alternatives, one technique that has never failed me till now is as shown here. All we're doing, is sending a lightweight XMLHttpRequest to our HTML file's host (our server). If the request is successful, we know that we have an active network connection. If the request fails, then it is safe to assume that either there is no internet connection or our server is down. We intentionally add a random number to the end of the URL as a query parameter; this makes our request URL "unique" and makes sure the browser actually tries to reach the URL via the network (instead of simply returning a possibly cached result)

It's easy to test this function before using it. Just open a new tab in Chrome, navigate to any URL, open Developer Tools and execute this function as an Immediately Invoked Function Expression (IIFE) in the Console.

(function(){ .. code here... })();

You will see that it returns true or false as expected. You can also have a look at the exact network request that was sent, using the 'Network' tab.

Let's go ahead and make a new method in our controller out of this code snippet.

Handling 'Submit' when online

This part is very straight forward. If our server is accessible when the user clicks on 'Submit', we simply push our data to our web service. We've already set up our controller's onSubmit method to be the 'Submit' button's 'press' event handler. If an internet connection is available, we just call our web service.

Preparing for offline storage

IndexedDB is a NoSQL database that is now available within all modern browsers for web applications to use. We can create multiple "tables" within our database. They're not called "tables" though, because they're not exactly tabular. They're called object stores, and they hold multiple objects of data (as opposed to multiple rows of data). While not comparable with SQL databases when it comes to versatility for very complex scenarios, there are a handful of features offered by IndexedDB at our disposal, such as indices and cursors. For more information, please refer to MDN's documentation.

Create an indexedDB object store

We'll now write a method for our controller called prepareIDB(). All it has to do is create a database and our object store, and get a reference to the database for later use.

Since most operations on an IndexedDB are asynchronous, every operation is represented by a request object. These objects emit events to represent various states of the operation they're representing. In the snippet below, the 'indexedDB.open' request emits various events such as upgradeneeded and success, that we can attach event handlers to.

If the OFFLINE_DB database doesn't already exist on the user's browser, the onupgradeneeded event handler is triggered, followed by onsuccess. If the database and object stores exist already, only the onsuccess handler is invoked.

In the onsuccess handler, we get a reference to our database and store it in the controller's myDB custom attribute. For every operation that we perform using our database (reads and writes), we will use this reference.

The upgradeneeded event actually indicates that the version of the database that you're trying to open isn't available (and not just that the requested database doesn't exist). For example, if you were to decide to add 2 new object stores to the same database, you'd add the db.createObjectStore() statements in the onupgradeneeded event handler. To inform the browser that you want onupgradeneeded to be triggered again even though the database already exists, you need to increment the 'database version number', which is the second parameter in our call to window.indexedDB.open().

We'll put a call to prepareIDB() inside the onInit() method of our controller.

By launching our application, we can check using Chrome's developer tools that our database and object stores are ready for action!

Write a method to write data

Our next objective is to create a helper method that accepts an object as a parameter and writes it to the object store. In our current demo application, the data to be written is extremely simply structured. Each record is just an object with two attributes - name and comment.

We will add a call to this method in the else part of our onSubmit controller method.

Write a method to push data from IndexedDB to server

If we're not connected to the network, we're writing the data to our local database. But sooner or later, this data should end up in the server. How do we manage to do that? One solution is to check for network connectivity every 2 seconds (while 2 seconds is easier to test, 2 minutes is probably more practical), and push all offline data to the server if available.

Now to make sure our checkAndSync() method is called every 2 seconds, we'll set up an interval in our controller's onInit() method.

Can't we directly write this.checkAndSync() instead? Isn't it the same thing?

No, it isn't. When the function that we're passing to window.setInterval is executed, it is executed with a different function context. That means, "this" will refer to the window object and not our controller, and thus the checkAndSync method won't be accessible. To overcome this, we take advantage of a very interesting feature of Javascript called "Closures". I've written a blog post on closures on SCN, which you might find useful.

Can't we write window.setInterval(oController.checkAndSync, 2000);?

Though syntactically correct, we can't. Can you figure out why?

Shouldn't we delete the objects in the object store after pushing them to the server?

Absolutely correct. We need to - we're just not covering it in this document however.

Caching resources for offline access

The cache manifest is a special file that you need to create and link your HTML page to. This file tells the browser which resources it should cache and make available offline. This file is usually either named cache.manifest or manifest.appcache. But before we create this resource, we need to know which resources to cache. The most straightforward way to do this, is by checking the 'Network' tab in Chrome's developer tool while your UI5 application is running. You can use it to get a list of CSS, JS and other resource that your application is requesting for while running.

When I tried running my app (I'm intentionally using the heavy sap-ui-core-all.js file), I noted down all the files that were being requested over the network, and added them to my manifest file to make them all available offline. A detailed description about the structure of this file can be found here.

Once an application cache is created on the user's browser, the cache contents are used even when the user is connected to the internet! The cache is discarded and updated only when the manifest file itself changes. That's why it's a good idea to have a comment in your file, say "# Version 1". This way, every time you update the version number, the manifest file becomes "changed", and the browser will discard its existing cache and create a new cache.

Cache files need to be served by your server with the special MIME type "text/cache-manifest". This is fairly easy to do if you're using an Apache server. You only need to add the following line within your <IfModule mime_module> section in httpd.conf.

AddType text/cache-manifest .manifest

If you're using HTTPS on your server (SSLEngine on option in Apache), you can only cache resources that are served using the HTTPS protocol. Finding it out (the hard way) just wasted 4 hours of my day today.

To link this manifest file to our HTML file, all we need to do is add another attribute to our <html> tag as shown below.

Now when I launch my application, it is interesting to see that none of the Javascript or CSS files are requested over the network - they're all served from the application cache!

Ready to roll.

Our application is finally ready. It's also so cool, it only needs an network connection when launched for the first time. It even sports its own offline IndexedDB NoSQL database to store data until it is pushed to the server.

Pretty neat, huh?

As always, your feedback and criticism are most welcome!

Former Member