Work offline using Application cache and Local Storage

If we want to work offline we should be able to. This is a simple sentence and until recently quite difficult, but now very achievable. Using two new browser features (html5 compatible browsers) this can be accomplished.

Local Storage: This is just a two dimensional array that allows storage of data pairs within a clients browser.
Application Cache: This reads an app cache manifest file that tells the browser what to download and cache, so that next time if the website is started up offline it will reload the old cached page (including css, js, images etc.)

Together this enables a user to load forms and pages without an internet connection, fill them out and save them ready for submission when back online. I created a nugget package called RockOffline which handles the local form storage. In the source code I have an included an example for setting up the Application Cache part.

Local form cache –Rock Offline nuget package includes initializer, main script and submit event, just needs the scripts loaded (see readme in package)

A form initialized by RockOffline nugget package, note the dataidval (this is the unique id for this form) and the class which tells us we want to make it a locally saved form

<formclass='persist-local offset3'dataidval='Person-1'id='diary-edit'action='@Url.Action('Save', 'Patient')" method='POST'>

The RockOffline initializer

$(document).ready(function () {
    $('.persist-local').each(function () {
        var $this = $(this);
        $this.rockOffline({
            // default is false
            persistPasswords: false,
            // default is false, persists in 
            // sessionStorage or to localStorage
            persistLocal: true,
            // takes jQuery selector of 
            // items you DO NOT want to persist
            skipSelector: null,
            // true by default, false will 
            // only persist on form submit
            autoPersist: true,
            id: $this.attr('dataidval'),
        });
    });

A form that has been saved in local storage Note the form values in the inspector.

If we close this page and reopen it will re-populate the form (providing the dataidval is set the same –this would probably be something like PersonEdit-@Model.Id and come from a list & edit CRUD type thing). As the purpose I will be using this for is an offline form with signature I included the JqueryUI signature pad for fun, the output id holds all the coordinates for that scribble!

Application Cache (offline page server)

We need to tell the browser that, when online we need to download a copy of the pages, and any associated resources:
Add the reference to the layout page:

<html lang=”en” manifest=”../manifest.appcache”>OR<html lang=”en” manifest=”@ViewBag.AppCacheManifest”>

and this (to make it easier when in development)

#if !DEBUGViewBag.AppCacheManifest = &quot;../manifest.appcache&quot;;#endif

Create the appcache manifest razor view (this can be static but is better to be dynamic). You need to play around a bit to ensure everything from the pages you want offline is in here. i.e. load the page from the cache and look in the console for errors.

@using System.ConfigurationCACHE MANIFEST@{Layout = null;Response.ContentType = &quot;text/cache-manifest&quot;;}#v4@Url.Action(&quot;Edit&quot;, &quot;Offline&quot;)@Url.Action(&quot;List&quot;, &quot;Offline&quot;)@Url.Action(&quot;Index&quot;, &quot;Home&quot;) @Url.Content(&quot;~/Images/heroAccent.png&quot;)@Url.Content(&quot;~/Images/accent.png&quot;)@Url.Content(&quot;~/Content/pen.cur&quot;) CACHE:@BundleTable.Bundles.ResolveBundleUrl(&quot;~/bundles/jquery&quot;)@BundleTable.Bundles.ResolveBundleUrl(&quot;~/bundles/bootstrap&quot;)@BundleTable.Bundles.ResolveBundleUrl(&quot;~/bundles/offline&quot;)@BundleTable.Bundles.ResolveBundleUrl(&quot;~/Content/css&quot;)@BundleTable.Bundles.ResolveBundleUrl(&quot;~/bundles/modernizr&quot;)@BundleTable.Bundles.ResolveBundleUrl(&quot;~/bundles/signature&quot;)
An example of an error, in this case a missing image

Then tie them together in the route table (the browser looks for WEBSITE/manifest.appcache

routes.MapRoute(null,&quot;manifest.appcache&quot;,new { controller = &quot;Shared&quot;, action = &quot;AppCacheManifest&quot; });

IIS/MVC route will cache this manifest.appcache i.e. not hit the razor view again and hence will not trigger the client to update their cache when online, to avoid this add the following to your config (inside the tag):

<add name="ScriptsHandler" path="*.appcache" verb="GET"type="System.Web.Handlers.TransferRequestHandler"preCondition="integratedMode,runtimeVersionv4.0" />

Once this page has been loaded once or more online the application cache is populated for that browser

Now it can be served by the same browser with no internet connection. In conjunction with the local form stage a user can continually fill out forms offline and then submit when online.

A form that is being edited offline

The Rock Offline submit will only hit the action method if the site is online, and when it does, clear the cache

1234567$('.persist-local').submit(function () {if (navigator.onLine) {localStorage.removeItem(form.attr('dataidval')); //remove it from the cachereturn true//submit}return false;});

This works beautifully as far as the testing has gone, although only in chrome so far. Key points about offline web pages are:

  • The user must visit the page once whilst online
  • The browser must be Html5 compatible (have local storage and application cache)
  • Security (log in, stolen computer etc.) needs to be considered at an application level
  • Manifest versioning has not really been discussed here (when a client should pull down a new page)
  • If we want to edit a record we would need a simple list with edit button (and probably a submit button when back online)
  • This is only working when bundling is on (no debug mode)

In a way this replaces things like RIA and windows/mobile device applications. It allows the one system to work across all devices. Obviously this is not perfect for all solutions i.e. when heavy security (unless encrypted) and when additional functionality is required i.e. above and beyond serializable data….but it seems cool to me.

Leave a comment