Chapter 15

Offline Applications

WHAT YOU WILL LEARN IN THIS CHAPTER:

  • Storing files locally in an offline cache
  • Storing data on the client using key-value storage
  • Accessing a local SQL database with JavaScript

In the past, one of the key differences between native IOS apps and web apps was the ability of native apps to work with local and remote data, whereas web apps were limited to working only when a live connection was available. However, Safari on IOS has embraced support for HTML 5”s offline capabilities, enabling you to create web apps that work even when the user has no access to the Internet.

In this chapter, I walk you through these offline capabilities.

HTML 5 OFFLINE APPLICATION CACHE

Safari on IOS takes advantage of HTML 5”s offline application cache to enable you to pull down remote files from a web server and store them in a local cache. Consequently, when the device is not connected to the Internet, either through 3G or Wi-Fi access, users can continue to work with your web app in offline mode.

You can include any file in the manifest that can be displayed locally without server-side processing — images (.jpg, .png, and .gif), HTML files, CSS style sheets, and JavaScript scripts.

Safari then attempts to download files in the manifest. If successful, Safari on IOS looks for these files in the cache first before going to the server. However, in the event of a missing file, incorrect URL, or other error, the update process fails and no further files are downloaded. Then, the next time the manifest is loaded, Safari attempts to download all files once again.

After Safari downloads the files in a manifest file, the cache is only updated in the future if the manifest file changes. (Note that the content of the manifest file is what is evaluated to determine whether or not to update the cache, not its last-saved date or other file attribute.) However, if you want to force an update, you can do so programmatically through JavaScript.

Create a Manifest File

To enable the offline cache, you need to create a manifest file that lists each of the files you want to have available offline. The manifest file is an ordinary text file, without any HTML or XML markup at all, that includes two parts:

  • Declaration: The manifest is declared by typing the following on the first line of the file:
    CACHE MANIFEST
  • URL Listings: The subsequent lines list the URLs for each file that you want to cache. The paths must be relative to the path of the manifest file.

You can also add comments to the file by adding a # to the start of each line.

Here’s an example manifest file used for a small web app.

image
CACHE MANIFEST
 
# images
jackarmitage.png
 
# in-use assets
../assets/global.css
../assets/cui.css
../assets/assets.js
../assets/whiteButton.png
../assets/toolButton.png
../assets/toolbar.png
../assets/toggleOn.png
../assets/toggle.png
../assets/thumb.png
../assets/selection.png
../assets/prev.png
../assets/pinstripes.png
../assets/next.png
../assets/loading.gif
../assets/listGroup.png
../assets/listArrowSel.png
../assets/listArrow.png
../assets/grayrow.png
../assets/grayButton.png
../assets/cancel.png
../assets/blueButton.png
../assets/blackToolButton.png
../assets/blackToolbar.png
../assets/blackButton.png
../assets/backButton.png

Code snippet example.manifest

After you have created the manifest file, you need to save it with a .manifest extension.

In order for offline cache to work correctly, you need to be sure that your web server serves up the manifest file correctly. Because HTML 5 offline cache is still an “emerging” technology, many ISPs do not provide built-in support for it. Therefore, check to make sure your server assigns the MIME type text/cache-manifest to the manifest extension. In my case, I had to add it as a custom MIME type.

Reference the Manifest File

When you have created the manifest file and uploaded it to your server, you need to link it into your web app by adding the manifest attribute to the root html tag of your web file:

<html manifest="prospector.manifest"xmlns="http://www.w3.org/1999/xhtml">

In this case, the prosector.manifest file is in the same directory as the index.html file.

Programmatically Control the Cache

You have access to the cache using the JavaScript window.applicationCache object from inside your web app. To force a cache update, you can use the update() method. When the update is complete, you can swap out the old cache with the new cache using the swapCache() method.

However, before you begin this update process, you need to check first to make sure that the application cache is ready for updating by checking the status property of the applicationCache object. This property returns one of the values shown in Table 15-1.

TABLE 15-1: applicationCache.status Values

CONSTANT NUMBER DESCRIPTION
window.applicationCache.UNCACHED 0 No cache is available
window.applicationCache.IDLE 1 Local cache is up to date
window.applicationCache.CHECKING 2 Manifest file is being checked for changes
window.applicationCache.DOWNLOADING 3 Safari is downloading changed files and added them to the cache
window.applicationCache.UPDATEREADY 4 New cache is ready for updates and overrides your existing cache
window.applicationCache.OBSOLETE 5 Cache is obsolete and is therefore being deleted

For example:

if (window.applicationCache.status == window.applicationCache.UNCACHED)
{
    alert("Houston, we have a cache problem");
}
else if (window.applicationCache.status == window.applicationCache.IDLE)
{
    alert("No changes are necessary.");
}
else
{
    alert("Let's do something with the cache.");
}

You can assign event handlers to the applicationCache object based on the results of the update process. For example:

var localCache = window.applicationCache;
localCache.addEventListener("updateready", cacheUpdateReadyHandler, false);
localCache.addEventListener("error", cacheErrorHandler, false);

The applicationCache events that are supported include the following:

  • checking
  • error
  • noupdate
  • downloading
  • updateready
  • cached
  • obsolete

Because I am focused on programmatically performing an update, I just listen for the updateready and error events:

// Handler when local cache is ready for updates
function cacheUpdateReadyHandler()
{
}
 
// Handler for cache update errors
function cacheErrorHandler()
{
alert("Houston, we have a cache problem");
}

After these event handlers are defined, you are ready to start the update and swap process.

// Handler when local cache is ready for updates
function cacheUpdateReadyHandler()
{
    localeCache.update();
    localeCache.swapUpdate();
}

The update() method updates the cache and then swapUpdate() replaces the old cache with the new cache you successfully downloaded.

Checking Connection Status

When you use application cache, you may have some online processing that you want to disable if you are in offline mode. You can check the connection status in your web app by checking the navigator objects onLine property:

if (navigator.onLine)
    alert("Online. All services available.")
else
    alert("Offline. Disabling currency rate updates.");

TRY IT OUT: Creating an Application Cache

The following example shows the application cache in action.

1. Create the following HTML document in your text editor and then save the document as BIDHJ-Ch15-Ex1.html.

image
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width,
   minimum-scale=1.0, maximum-scale=1.0">
<title>Cache Me If You Can</title>
</head>
<body>
<div id="progressSpinner" style="display:none;">
<p>Loading...<img src="spinner.gif" alt="Loading..." width="16" height="16" /></p>
</div>
<div id="content">
<div id="onlineIndicator">online|offline</div>
<p>This is a test of the Safari on IOS applicationCache.</p>
<img src="boy.png" />
</div>
</body>
</html>

Code snippet BIDHJ-Ch15-Ex1.html

2. Add the following script code inside the document head:

image
<script type="text/javascript">
 
    // Assign var to applicationCache
    var localCache = window.applicationCache;
 
    // Show progress indicator
    localeCache.addEventListener("progress", cacheProgressHandler, false);
    // Swap cache
    localeCache.addEventListener("updateready", cacheUpdateReadyHandler, false);
    // Hide progress indicator
    localeCache.addEventListener("cached", cacheNoUpdateHandler, false);
    localeCache.addEventListener("noupdate", cacheNoUpdateHandler, false);
    // Show error msg
    localeCache.addEventListener("error", cacheErrorHandler, false);  
 
    // Called on load
    function init()
    {
      if (navigator.onLine)
          document.getElementById("onlineIndicator").textContent = "online"
      else
          document.getElementById("onlineIndicator").textContent = "offline";
 
      localeCache.update();   
 
    }
 
    // Called when no updates are needed            
    function cacheNoUpdateHandler()
    {
        document.getElementById("progressSpinner").style.display = "none";
    }
 
    // Called when a cache upate is in progress
    function cacheProgressHandler()
    {
      document.getElementById("progressSpinner").style.display = "table";
    }
 
    // Called when cache is ready to be updated
    function cacheUpdateReadyHandler()
    {
      localCache.swapCache();
      document.getElementById("progressSpinner").style.display = "none";
    }
 
    // Error handler
    function cacheErrorHandler()
    {
      document.getElementById("progressSpinner").style.display = "none";
      alert("A problem occurred trying to load the cache.");
    }          
 
</script>

Code snippet BIDHJ-Ch15-Ex1.html

3. Add an onload event handler to the body tag:

image
<body onload="init()">

Code snippet BIDHJ-Ch15-Ex1.html

4. Add a manifest declaration to the HTML tag and then save the document:

<html manifest="BIDHJ-Ch15-Ex1.manifest" xmlns="http://www.w3.org/1999/xhtml">

5. Create the following text file and then save the document as BIDHJ-Ch15-Ex1.manifest.

image
CACHE MANIFEST
 
#  images
boy.png
spinner.gif

Code snippet BIDHJ-Ch15-Ex1.manifest

How It Works

  • In the HTML document, the localeCache variable is assigned to the window.application Cache. Handlers are then assigned to applicationCache events, which determine whether or not to show a progress indicator of the cache updating process. If the updateready event is triggered, then the swapUpdate() method is called to update to cache. Figures 15-1 and 15-2 show this example run in both online and offline modes.

USING KEY-VALUE STORAGE

In addition to local cache, Safari on IOS also supports HTML 5 key-value storage as a way to provide persistent storage on the client either permanently or within a given browser session. Key-value storage bears several obvious similarities to cookies in client-side storage. However, although cookies are sent back to the server, saved key-value data is not sent back to the server unless you explicitly do so through JavaScript. You also have greater control over the data persistence and window access to that data using key-value storage.

You can specify whether the key values you are saving should be long term or short term by working with two different JavaScript objects: localStorage and sessionStorage.

  • Use localStorage when you want to store a key-value pair permanently across browser sessions and windows.
  • Use sessionStorage for storing temporary data within a given browser window and session.

Saving a Key Value

You can save a key value in one of two ways. First, you can call the setItem() method on the localStorage or sessionObject object.

localStorage.setItem(keyName, keyValue);

For example, to save a user-inputted value as the firstName key for long-term storage, use the following:

localStorage.setItem("firstName", document.getElementById("first_name").value);

Second, a shortcut to save a value is to treat the key-value name as an actual property of the localStorage or sessionStorage objects. For example, you could also write the previous example as the following:

localStorage.firstName = document.getElementById("first_name").value);

However, if you use this shortcut method, you need to make sure that the name of your key value is a valid JavaScript token.

Any local database is going to have a maximum capacity, so it is good practice to trap for a possible exception in case the capacity has been reached. To do so, you’ll want to trap for QUOTA_EXCEEDED_ERR. For example:

try
{
    localStorage.firstName = document.getElementById("first_name").value);
}
catch (error)
{
  if (e == QUOTA_EXCEEDED_ERR)
      alert("Unable to save first name to the database.");
}

Whenever you interact with the local storage database, a storage event is dispatched from the body object. Therefore, to handle this event, you can attach a listener to the body:

document.body.addEventListener("storage", storageHandler, false);

The event object that is passed to the handler enables you to get at various pieces of the transaction (see Table 15-2). For example, the following handler outputs the storage event details to an alert box:

TABLE 15-2: storageevent Properties

PROPERTY DESCRIPTION
url URL of the page that calls the storage object. Returns null if the requesting page is not in the same window. Returns undefined if calling from a local page.
key Specifies the key that has been added, changed, or removed.
newValue Provides the new value for the key.
oldValue Provides the old value for the key. If no key was previously defined, null is returned.
window Reference to the window object that called the storage object. Returns null if the requesting page is not in the same window.
      function storageHandler(event)
      {
          var info = "A storage event occurred [" +
                     "url=" + event.url + ", " +
                     "key=" + event.key + ", " +
                     "new value=" + event.newValue + ", " +
                     "old value=" + event.oldValue + ", " +
                     "window=" + event.window + "]";
 
          alert(info);
      }

Loading Key-value Data

You can load data from the localStorage and sessionStorage objects by calling the getItem() method:

var keyValue = sessionStorage.getItem(keyName);

For example:

var accessCode = sessionStorage.getItem("accessCode");

Or, as you would expect, you can also access a key value by calling it as a direct property of the respective object:

var accessCode = sessionStorage.accessCode;

If the key value that you request is not located, then a null value is returned:

if (sessionStorage.accessCode != null)
{
    var valid = processCode(sessionStorage.accessCode);
}

Deleting Key-value Data

You can remove a specific key-value pair or clear all keys from the local key-value database.

To remove a specific key-value pair, use removeItem():

localStorage.removeItem(keyName);

To remove all keys, use one or both of the following:

// Remove all permanent keys
localStorage.clear()
// Remove all session keys
sessionStorage.clear()

TRY IT OUT: Using Key-value Storage

To demonstrate, this example walks you through the steps needed to use HTML 5”s key-value storage mechanisms to save permanent and temporary values.

1. Create the following HTML document in your text editor and then save the document as BIDHJ-Ch10-Ex2.html.

image
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width,
     minimum-scale=1.0, maximum-scale=1.0">
    <title>Value Keys</title>
</head>
 
<body onload="init()">
<div>
<p>Define a key value in which you want to save permanently:</p>
 
<input id="localValue" onchange="setLocalKeyValue("localValue")"/>
 
<p>Define a key value in which you want to save for this session only:<p>
 
<select id="sessionValue" onchange="setSessionKeyValue("sessionValue")">
<option value="UN">(Select State)</option>
<option value="MA">Massachusetts</option>
<option value="ME">Maine</option>
<option value="RI">Rhode Island</option>
<option value="VT">Vermont</option>
</select>
</div>
 
<p>
<button onclick="clearAll()">Clear All</button><br />
</p>
<div id="statusDiv">Start session</div>
</body>

Code snippet BIDHJ-Ch15-Ex2.html

2. Add the following JavaScript code inside the document head and save the file:

image
<script language="JavaScript" type="text/javascript">
 
      // Check to see whether or not key-value storage is available
      var localStorageAvail = typeof(localStorage) != "undefined";
      var sessionStorageAvail = typeof(sessionStorage) != "undefined";
 
      // Assign event listeners
      window.addEventListener("onload", init, false);     
      window.addEventListener("onbeforeunload", beforeUnloadHandler, false);
 
      // Called on load
      function init()
      {
          // If no storage is available
          if (!localStorageAvail || !sessionStorageAvail)
            setStatus("Key value storage is not supported.");
          // Otherwise, assign handler and retrieve any previously stored values
          else
          {
             document.body.addEventListener("storage", storageHandler, false);
             loadValues();    
          } 
      }
 
      // Save local key
      // value = id of the element whose value is being saved 
      function setLocalKeyValue(value)
      {
          if (localStorage)
          {
              localStorage.setItem(value, document.getElementById(value).value);
              setStatus(document.getElementById(value).id +
                " saved as a local key.");
          }
      }
 
      // Save session key
      // value = id of the element whose value is being saved 
      function setSessionKeyValue(value)
      {
          if (sessionStorage)
          {
              sessionStorage.setItem(value,
                document.getElementById(value).selectedIndex);
              setStatus(document.getElementById(value).id
                 + " saved as a session key.");
          }
      }
 
      // Loads key-value pairs from local storage
      function loadValues()
      {
          // Is localValue defined? If so, then assign its value to the text box.
          if (localStorage.localValue)
              document.getElementById("localValue").value =
                localStorage.localValue; 
          // Is sessionValue defined? If so, then assign
          // its value as the index to the sessionValue select.
          if (sessionStorage.sessionValue)
              document.getElementById("sessionValue").selectedIndex =
                sessionStorage.sessionValue;
      }
 
      // Clear all key-value pairs     
      function clearStorage()
      {
          // Clear local database
          sessionStorage.clear();
          localStorage.clear();
 
          // Clear UI as well
          document.getElementById("localValue").value = "";
          document.getElementById("sessionValue").selectedIndex = 0;
      }
 
      // Save current state
      function saveChanges()
      {
          setLocalValue("localValue");
          setSessionKeyValue("sessionValue");
          // Used to return to the window
          return null;
      }
 
      // Be sure and save changed before a window is closed
      function beforeUnloadHandler()
      {
          return saveChanges();
      }
 
      // Listener for all storage events. Outputs to the status div.
      function storageHandler(event)
      {
          var info = "A storage event occurred [" +
                     "url=" + event.url + ", " +
                     "key=" + event.key + ", " +
                     "new value=" + event.newValue + ", " +
                     "old value=" + event.oldValue + ", " +
                     "window=" + event.window + "]";
 
          setStatus(info);
      }
 
      //  Utility function that outputs specified text to the status div
      function setStatus(statusText)
      {
          var para = document.createElement("p");
          para.appendChild(document.createTextNode(statusText));
          document.getElementById("statusDiv").appendChild(para);
      }
 
</script>

Code snippet BIDHJ-Ch15-Ex2.html

How It Works

In this example, an input element value is saved as a permanent key-value pair, whereas the selection in a select list is saved for the current session only. I want to save the values of these elements each time they change, so I assign onchange handlers to both of these elements.

I also want to add a button to clear the key values on demand as well as a status box that acts as a console to output the storage events that are taking place. Figure 15-3 shows the page.

With the HTML markup ready, I can look at the JavaScript code needed to power this example. To begin, I want to confirm that key-value storage is available, so I do a test on the localStorage and sessionStorage objects.

I can then check one or both of these values prior to attempting to save or load storage data.

To save the value of the input element permanently, I add the setLocalKeyValue() function. Next, to save the selectedIndex of the select element as a session-only key-value pair, I use the setSessionKeyValue() function.

To retrieve the values of these key-value pairs, I use the loading function named loadValues(). Next, to clear all of the local values, I define a clearStorage() function to remove key-value pairs from both sessionStorage and localStorage objects as well as the UI fields.

The following figures demonstrate this example when run. Figure 15-4 shows the data being entered on screen. Figure 15-5 is what’s shown after pressing the Refresh button within the current session. As you can see, both values are retained. However, after closing out that window and calling the URL again, the session value is cleared, as shown in Figure 15-6.

When you are working with key-value storage, I recommend using the Web Inspector that is provided with the Windows and Mac versions of Safari (you can find it on the Develop menu). The Databases panel (see Figure 15-7) enables you to see the current state of the key-value pairs for the page you are working with.

GOING SQL WITH THE JAVASCRIPT DATABASE

When you begin to develop more substantial web applications, you may easily have local storage needs that go beyond simple caching and key-value pair persistence. Perhaps your app stores application data locally and periodically syncs with a database on a backend server. Or maybe your web app uses a local database as its sole data repository. Using HTML 5”s database capabilities, you can access a local SQLite relational database right from JavaScript to create tables, add and remove records, and perform queries.

Open a Database

Your first step is to open a database by calling the openDatabase() method of the window object:

var db = window.openDatabase(dbName, versionNum, displayName, maxSize);

The dbName parameter is the database name stored locally; versionNum is its version number; displayName is the display name of the database used by the browser (if needed); and maxSize is the maximum size in bytes that the database will be (additional size requires user confirmation). For example:

var db = window.openDatabase("customers", "1.0", "Customer database", 65536);

After the database is open, you can perform a variety of tasks on the database.

Each task you perform must be part of a transaction. A database transaction can include one or multiple SQL statements and is set up as follows:

db.transaction(transactionFunction, successCallbackFunction,
    errorCallbackFunction,);

Querying a Table

Suppose you want to perform a simple query on a customer table in your database. You could set up the query inside of a transaction, such as the following:

var db = window.openDatabase("customers", "1.0", "Customer database", 65536);
 
if (db)
{
    var updateSqlStr = "SELECT * FROM customers";
    db.transaction( function(transaction) {
      transaction.executeSql(updateSqlStr) },
        successHandler, errorHandler);
}
 
function successHandler(result)
{
    if (result.rows.length > 0)
    {
         for (var i=0; i<result.rows.length; i++)
         {
            var r = results.rows.item(i);
            alert(r["first_name"] + " was retrieved from the database.");
    }
}
 
function errorHandler(error)
{
    alert("An error occurred when trying to perform a query.");
}

In this example, the db.transaction calls a SQL execute statement to return all of the customers from the database. The successHandler() function is called when the database is successful. The errorHandler() function is called if the operation failed.

The result object returned contains a rows array that contains each record in the returning set. You can access each of the fields by specifying its field name inside the brackets.

EXERCISES

1. What is the purpose of a manifest file?

2. Do you need to do anything special on the web server to use a manifest file?

3. What’s the difference between HTML 5 key-value storage and cookies?

Answers to the Exercises can be found in Appendix A.

• WHAT YOU LEARNED IN THIS CHAPTER

TOPIC KEY CONCEPTS
Enabling offline cache Create a manifest file that lists the files you want to store offline.
Loading local assets Access your offline cache using the window.applicationCache object.
Saving and loading key values Use the setItem() and getItem() methods of the localStorage or sessionObject.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.28.12