Monday, August 29, 2011

Persistent local storage in a PhoneGap app using HTML5

I was trying to save a few bits of data across webkit browser invocations in a PhoneGap app.  My first approach was to save the data in a file, using PhoneGap's File API.  That got tedious fast.  Lucky for me, PhoneGap contributor Bryce suggested that I make use of a simple, new feature in HTML5: persistent local storage.

Google returned a good reference on the first search:  http://diveintohtml5.org/storage.html

I wrote two sandbox programs with three buttons to demonstrate how to save, fetch, and delete.  The first program handles a single string.

String demo


<!DOCTYPE HTML>
<html>
    <head>
        <title>HTML5 Persistent String</title>
        
        <script type="text/javascript" charset="utf-8">
        
            //---------------------------------------
            // Button handlers
            //---------------------------------------
            function setit() {
                var stringValue = "Hello World!";
                localStorage.setItem("string_key", stringValue);
                document.getElementById("stringResponseText").innerHTML = 
                    "Set string value: " + stringValue;
            }
            function nullit() {
                localStorage.setItem("string_key", null);
                document.getElementById("stringResponseText").innerHTML = 
                    "Set string value to null.";
            }
            function getit() {
                var stringValue = localStorage.getItem("string_key"); 
                document.getElementById("stringResponseText").innerHTML = 
                    "Got string value: " + stringValue; 
            }
        </script>
    </head>
    <body bgcolor="lightgreen">
        <h2>HTML5 Persistent String</h2>
        <input type="button" value="set" onclick="setit()" />
        <input type="button" value="get" onclick="getit()" />
        <input type="button" value="null" onclick="nullit()" />
        <p>
        <p id="stringResponseText"> </p>        
    </body>
</html>



Object demo

The second program follows the same pattern, but with a user-defined javascript object.  The secret here is to use JSON.stringify to convert your object into a 'flat' string for saving, and JSON.parse() to reconstitute the object after it is read.


<!DOCTYPE HTML>
<html>
    <head>
        <title>HTML5 Persistent Object</title>
        
        <script type="text/javascript" charset="utf-8">
        
            //---------------------------------------
            // Object definition and accessor
            //---------------------------------------
            function myObject(var1, var2, var3) {
                this.one = var1;
                this.two = var2;
                this.three = var3;
                this.toString = toString;
            }
            function toString() {
                return "one=" + this.one +
                       " two=" + this.two + 
                       " three=" + this.three;
            }
            
            //---------------------------------------
            // Button handlers.            
            //---------------------------------------
            function setIt() {
                var testObject = new myObject( "bird", "cat", "dog" );
                localStorage.setItem("object_key", JSON.stringify(testObject));
                document.getElementById("objectResponseText").innerHTML = 
                    "Set object value: " + testObject.toString();
            }
            function nullIt() {
                localStorage.setItem("object_key", null);
                document.getElementById("objectResponseText").innerHTML = 
                    "Set object value to null.";             
            }
            function getIt() {
                var stringifiedObject = localStorage.getItem("object_key");
                var reconstitutedObject = JSON.parse(stringifiedObject);
                if (reconstitutedObject) {
                    reconstitutedObject.toString = toString;
                    document.getElementById("objectResponseText").innerHTML = 
                        "Got object value: " + reconstitutedObject.toString(); 
                }
                else {
                    document.getElementById("objectResponseText").innerHTML = 
                        "Object value not found.";
                }
            }
        </script>
    </head>
    <body bgcolor="lightblue">
        <h1>HTML5 Persistent Object</h1>
        <h3>Object</h3>
        <input type="button" value="set" onclick="setIt()" />
        <input type="button" value="get" onclick="getIt()" />
        <input type="button" value="null" onclick="nullIt()" />
        <p>
        <p id="objectResponseText"> </p>        
    </body>
</html>




Testing

These sandbox programs can be browsed directly with a browser which supports HTML5, such as Chrome or Safari.

They can also be bundled into a PhoneGap app (via index.html), where they will work well installed on a mobile device.

Hope this is useful!

Friday, August 19, 2011

Including an optimized Dojo build within a PhoneGap app

In my last post, I showed how to create an optimized build of the Dojo Toolkit.  An optimized build contains the exact pieces of Dojo needed by your app, with nothing extra.  I served my app and the optimized Dojo from an Apache web server, and saw that it downloaded really fast.

Today I wondered if the same optimized build technique could be used in a hybrid mobile app created with the PhoneGap framework.  PhoneGap lets you build installable mobile apps where your app's content is written in HTML and JavaScript instead of each phone's proprietary language.  I had written hybrid apps which fetched Dojo from the Google CDN, and it worked fine.  But each time an app started, it had to download Dojo from the CDN.  That took time and bandwidth.  My goal was to include Dojo with my app, and see if it loaded instantly.

To my pleasant surprise, the optimized build worked exactly as expected.  Here is what I did...

First, I found an existing 'HelloPhoneGap' project for Android.  (You can create one from scratch using the instructions at Getting Started for your favorite mobile platform.)  The entry point for your app in a PhoneGap build environment is an index.html file within an assets/www directory.  For example:

.../HelloPhoneGap/assets/www/index.html

I modified the contents of index.html to point to the buildtest.html file created previously...


<!DOCTYPE HTML>
<html>
  <head>
    <title>PhoneGap</title>
  </head>
  <body bgcolor="yellow" >
  <h1>HelloPhoneGap</h1>
  <h2>from assets/www/index.html</h2>
  <ul>
      <li><a href="buildtest.html">optimized dojo build</a> 
  </ul>
  </body>
</html>



I copied the three files and directories created from the optimized build into the PhoneGap directory:

.../HelloPhoneGap/assets/www/buildtest.html
                             dojo/dojo.js
                             my/app.js

Then I compiled, installed, and started the app.  It worked out of the box.

When I started the app, the index.html file appeared.



I clicked the link to jump to the optimized dojo build page, buildtest.html.  The alert popped up:



After clicking 'OK', I got the version information.



Amazing.

Wednesday, August 17, 2011

How to create an optimized build for the Dojo Toolkit

Here is the step-by-step procedure I used to create an optimized dojo build.  This creates a dojo build which contain exactly what is needed by my app, no more, no less.    Using an optimized dojo build results in fewer downloads from the server, and less data transferred overall.

This procedure is based upon two reasonably good references:
It took me a while to make it all work because the filesystem locations were not obvious to me. 

Design Pattern

Adopt a design pattern where all your require() statements for dojo code are located in one javascript file.  This allows the dojo build process to create one new javascript file with the same name, to replace your original.  The new file replaces your list of dependents with the actual dojo javascript code.  

Prereqs

Fetch a dojo source build, for example:
Unzip this in a sandbox directory on your machine, for example:
    /home/sag/sandbox/dojo-release-1.6.1-src
where you will find subdirectories dojo, dijit, and dojox.

Coding

Here are two simple files I created, based upon first reference.  First the HTML:

/home/sag/sandbox/dojo-release-1.6.1-src/buildtest.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Tutorial: Hello Dojo!</title>

    <!-- load Dojo -->
    <script src="dojo/dojo.js"></script>

    <script>
        dojo.require("my.app");

        function init() {
            alert("Dojo ready, version:" + dojo.version);
            dojo.byId("greeting").innerHTML += 
                ". Greetings from dojo " + dojo.version;
        }
        dojo.ready(init);
    </script>
</head>
<body>
    <h1 id="greeting">Hello</h1>
</body>
</html>

and then the javascript:

/home/sag/sandbox/dojo-release-1.6.1-src/my/app.js

    dojo.provide("my.app");

    dojo.require("dojox.mobile.parser");
    dojo.require("dojox.mobile");
    dojo.require("dojox.mobile.compat");

You are now set up to do the build for this HTML/javascript.

Build

Go into the util/buildscripts directory.  For example:
    cd /home/sag/sandbox/dojo-release-1.6.1-src/util/buildscripts

Issue the build command.  Specify the path to your HTML file.  For example:
    ./build.sh 
      action=release 
      htmlFiles=/home/sag/sandbox/dojo-release-1.6.1-src/buildtest.html
It runs for a minute or so.

Inspect the results.  The important files are a new dojo.js and new app.js
    /home/sag/sandbox/dojo-release-1.6.1-src/release/dojo/dojo/dojo.js
                                                         /my/app.js

Test

Move the three files to the filesystem of your webserver.  For example, for apache:
    /var/www/html/dojo/dojo-opt/buildtest.html
                               /dojo/dojo.js
                               /my/app.js

Browse to the HTML and verify it works.

Verify Improvement

I captured screenshots of the downloads required when fetching the original HTML/javascript with the original dojo build.  It required more than a dozen file downloads.  (click the photo to enlarge)


Fetching the new HTML/javascript with optimized dojo results in three, much smaller downloads.