X

The Visual Builder Cloud Service Blog

  • July 3, 2019

Adding Offline Capabilities to an Oracle Visual Builder App

Shay Shmeltzer
Director of Product Management - Oracle
This is a syndicated post, view the original post here

Visual Builder allows you to create applications that can continue to function even when your device is disconnected from the network. To do that Visual Builder leverages the Oracle JET offline persistence toolkit. This toolkit enables your application to cache data on the client and serves it back from the cache when you are issuing the same REST call but your device doesn't have access to the server. It also allows you to "execute" operations such as adding records while offline - storing those requests on the client again - and then automate replaying them back when you are connected.

In the demo video below I show how to add some of these capabilities to your application. It's important to note that adding offline capabilities requires knowledge in JavaScript coding and an understanding of the offline toolkit. This is not a simple drag and drop operation - so approach carefully. Leverage the extensive logging that the offline persistence can do and monitor it's messages in the browser's dev tools console to see what is happening when. In the video you'll also see how to clear the cache on the client which is sometimes needed to see the real functionality.

The Visual Builder developer guide has a section that explains the concepts of offline persistence, and provides a basic starting point code sample. In the video below, I start from that code sample, then I modify it to change the cache strategy and implement support for POST operations and synch data changes on demand.

Here is a breakdown of things you'll see in the video (allowing you to skip to the section you need):

  • 0:00-1:30 - Exposing Oracle Database Table (Oracle ATP) with REST Services using ORDS and SQLDeveloper
  • 1:30-4:45 - Building a simple Read/Create Visual Builder App Accessing above ORDS services
  • 4:45-8:00 - Adding caching based on the book's sample
  • 8:00-10:30 - Switching to use cache data only when offline
  • 10:30- End - Adding handling of POST(Create) operations

As you can see the interaction with the offline persistence layer is managed in the application's JavaScript area. Below is the code used in my app, with the parts that I change from the doc sample explained below.

define([
    'vbsw/helpers/serviceWorkerHelpers',
    /**
     * Add the following entries to include the toolkit classes that you'll use. More information about these
     * classes can be found in the toolkit's API doc. See the link to the API doc in the paragraph before 
     * this sample file.
     * 
     */
    'persist/persistenceManager',
    'persist/defaultResponseProxy',
    'persist/persistenceUtils',
    'persist/fetchStrategies',
    /**
     * Add the following entry to enable console logging while you develop your app with the toolkit.
     */
    'persist/impl/logger'
  ],
  function(ServiceWorkerHelpers, PersistenceManager, DefaultResponseProxy,
    PersistenceUtils, FetchStrategies, Logger) {
    'use strict';

    function AppModule() {}

    function OfflineHandler() {
      /**
       * Enable console logging of the toolkit for development testing
       */
      Logger.option('level', Logger.LEVEL_LOG);
      Logger.option('writer', console);

      var options = {
        /**
         * The following code snippets implements the toolkit's CacheFirstStrategy. This strategy 
         * checks the application's cache for the requested data before it makes a request to cache 
         * data. The code snippet also disables the background fetch of data.
         */
        requestHandlerOverride: {
          handlePost: handlePost
        },
        fetchStrategy: FetchStrategies.getCacheIfOfflineStrategy({
          backgroundFetch: 'disabled'
        })
      };
      this._responseProxy = DefaultResponseProxy.getResponseProxy(options);
    }

    OfflineHandler.prototype.handleRequest = function(request, scope) {
      /**
       * (Optional). Write output from the OfflineHandler to your browser's console. Useful to help 
       * you understand  the code that follows.
       */
      console.log('OfflineHandler.handleRequest() url = ' + request.url +
        ' cache = ' + request.cache +
        ' mode = ' + request.mode);

      /**
       * Cache requests where the URL matches the scope for which you want data cached.
       */
      if (request.url.match(
          'https://yourserver.oraclecloudapps.com/ords/shay'
        )) {

        return this._responseProxy.processRequest(request);
      }
      return PersistenceManager.browserFetch(request);
    };

    OfflineHandler.prototype.beforeSyncRequestListener = function(event) {
      return Promise.resolve();
    };
    OfflineHandler.prototype.afterSyncRequestListener = function(event) {
      return Promise.resolve();
    };
    AppModule.prototype.createOfflineHandler = function() {
      /** Create the OfflineHandler that makes the toolkit cache data URLs */
      return Promise.resolve(new OfflineHandler());
    };
    AppModule.prototype.isOnline = function() {
      return ServiceWorkerHelpers.isOnline();
    };
    AppModule.prototype.forceOffline = function(flag) {
      return ServiceWorkerHelpers.forceOffline(flag).then(function() {
        /** if online, perform a data sync */
        if (!flag) {
          return ServiceWorkerHelpers.syncOfflineData();
        }
        return Promise.resolve();

      }).catch(function(error) {
        console.error(error);
      });
    };
      AppModule.prototype.dataSynch = function() {
        return ServiceWorkerHelpers.syncOfflineData();
      };

    // custom implementation to handle the POST request
    var handlePost = function(request) {
      if (ServiceWorkerHelpers.isOnline()) {}

      return PersistenceUtils.requestToJSON(request).then(function(
        requestData) {
        console.log('Inside PersistenceUtils');
        console.log(requestData);
        requestData.status = 202;
        requestData.statusText = 'OK';
        requestData.headers['content-type'] = 'application/json';
        requestData.headers['x-oracle-jscpt-cache-expiration-date'] =
          '';

        // if the request contains an ETag then we have to generate a new one
        var ifMatch = requestData.headers['if-match'];
        var ifNoneMatch = requestData.headers['if-none-match'];

        if (ifMatch || ifNoneMatch) {
          var randomInt = Math.floor(Math.random() * 1000000);
          requestData.headers['etag'] = (Date.now() + randomInt).toString();
          requestData.headers['x-oracle-jscpt-etag-generated'] =
            requestData.headers['etag'];
          delete requestData.headers['if-match'];
          delete requestData.headers['if-none-match'];
        }
        return PersistenceUtils.responseFromJSON(requestData);
      });
    };


    return AppModule;
  });

 

To highlight the major changes in the code done in the video:

  • Line 40 - changed the fetch strategy to fetch data from the cache only when offline - doc 
  • Line 37-39 - define a new method that will be used to handle POST operations
  • Lines 98-125 - the custom method that handles POST operations (line 11,19 contain the extra needed library reference)
  • Lines 93-95 - AppModule function to force synchronization of offline changes

Keep in mind that offline caching should be used with an understanding of the risks involved. For example, your cached data can get stale and not match the real situation in the server.So while caching can be a nice performance boost, you should probably use it for data that is not frequently changing. Also since data is cached on the client, other people could access it if they get access to the client. Don't cache sensitive data that you don't want falling into the wrong hands if you loose your device.

 

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.