8. Web Storage and Offline Web Applications

The greater complexity of web applications leads to an increase in the network bandwidth used. Although the capacity of data lines continues to increase as well, ways need to be found to optimize these transmissions by reducing them. Up until now, there was only one standardized method of storing information on the client side: cookies. Given that each cookie belonging to a website is transmitted fully from the client to the server with each call of the site, cookies should not be excessively large. In addition, web servers limit the maximum size of HTTP request fields, for example, to 8KB in the Apache server’s default setting.

The solutions suggested by the WHATWG fall into two categories, which are both discussed in this chapter. On one hand, the WHATWG envisions a “Storage interface” with persistent storage for sessions and storage that is not restricted to one session. On the other hand, controlled by a central configuration, files can be defined that the browser stores locally to be able to access them even without a network connection. Both approaches are very straightforward and simple, yet robust.

8.1 Storage

A structured client-side storage that exceeds the meager cookie limit has long been requested as an extension of the World Wide Web. Adobe integrated a function for storing data locally in the Flash Player with version 6, calling this technique Local Shared Object (LSO). The default setting of LSOs is 100KB, but it can swell to 10MB if required (after confirmation by the user). The problem with LSOs, often also referred to as Flash cookies, is that they can only be used with Flash and therefore fall outside the browser’s security model. Even if a user deletes all his browser cookies, a website can still track the user via Flash cookies. According to Wikipedia, more than half of the top websites on the Internet use Flash cookies to analyze user behavior.

The WHATWG has recorded its deliberations on the subject in its Web Storage document. Although Web Storage was removed from the core of the HTML5 specification, it is still most definitely related to it. Currently, the W3C specification is still at the Editor’s Draft stage, but because the implementation has been stable in all common browsers for some time (see section 8.3, Browser Support), significant future changes are unlikely.


Note

image

The current version of the W3C’s Web Storage specification can be found at http://dev.w3.org/html5/webstorage.

The WHATWG version is available at http://www.whatwg.org/specs/web-apps/current-work/complete/webstorage.html.


8.1.1 The “Storage” Interface

The “Storage” interface defines the common attributes and access methods of the persistent storage. Regardless of whether it is a sessionStorage or a localStorage object, both contain the methods or attributes presented in Table 8.1.

Table 8.1 Methods and attributes of the “Storage interface”

image

Similar to cookies, the Storage interface manages key/value pairs, where the key has the type DOMString. According to the W3C DOM specification, DOMStrings are strings encoded in UTF-16, which means you could even use special characters as key values, for example the German umlauts (ü, ö, ä). You could, but usually it is advisable not to; instead, it is better to use only characters and numbers from the US-ASCII character set. Even an empty string is a legal key but is usually not chosen on purpose. If an already existing key is used in the function setItem, the existing value is replaced by the new one.

Apart from setItem() and getItem(), the Web Storage API also offers another access method, which is often easier to read. If you, for example, want to save the key currentTemp with the value 18 in localStorage, the following line is enough:

localStorage.currentTemp = 18;

Not surprisingly, the value can also be read back this way:

alert(localStorage.currentTemp);

If localStorage contains an unknown number of items, the “Storage interface” method key works well:

for (var i=0;i<localStorage.length;i++) {
  var item =
    localStorage.getItem(localStorage.key(i));
    alert("Found item "+item);
  }

The specification states that values can be of any type, but the current browser implementations save all values as strings. To save complex data types such as arrays or objects, they have to be converted to strings first. An elegant way of doing this is via the JSON library:

JSON.stringify(itemsObject)

How much disk space the browser should reserve for the website is only hinted at in the specification. The recommended limit for the storage space that can be used per origin is 5MB (see section 8.1.3, “localStorage”). The current browser implementations adhere to this recommendation.

8.1.2 “sessionStorage”

One problem with using cookies is that the cookie is directly connected to the website and is independent of the browser window. The problem can become acute in the following example: A web shop saves the desired shopping cart in a cookie on the browser. If you open a second browser window while shopping and start shopping under a different name in that window, the products in the original window may change as well.

Although cookies can apply to several windows, the validity of sessionStorage is limited to the current browser window, which can be desirable in many cases. Figure 8.1 shows the difference between the two approaches using a simple example.

Figure 8.1 Two windows demonstrating the difference between “sessionStorage” and cookies

image

The central part of the JavaScript code for the example in Figure 8.1 looks like this:

window.onload = function() {
  var currDate = new Date();
  sessionStorage.setItem("currenttime",
    currDate.toLocaleString());
  document.cookie =
    "currenttime="+currDate.toLocaleString();
  updateHTML();
}
function updateHTML() {
  document.getElementById("currenttime").innerHTML =
    sessionStorage.getItem("currenttime");
  document.getElementById("currtimeCookie").innerHTML
    = getCookie("currenttime");
}

As soon as the website is loaded (window.onload function), the current date (including the time) is saved in both sessionStorage and the cookie. The updateHTML function inserts the relevant values in two HTML elements on the website. If the website is opened in two different browser windows, opening the second window will overwrite the cookie variable currenttime. If you then call the updateHTML function in the first window, the contents of sessionStorage and cookie differ.

In the specification, sessionStorage is assigned to the top-level browsing context. Simply put, this context can be seen as an opened browser window or an opened tab within a browser window. A nested browsing context would be, for example, an iframe within an HTML document. The browser also must ensure that each website has access only to its own sessionStorage and cannot read the contents of other websites. If this context is no longer accessible (the browser window or tab were closed), the browser can permanently delete the associated data.

8.1.3 “localStorage”

In contrast to sessionStorage, localStorage refers only to the origin of the website, not to the browser context. The origin is derived from the URL and consists of the used scheme in lowercase (for example, http), the server name (also in lowercase), and the port. If the port is not explicitly stated, the scheme’s default port is used (for HTTP, it would be 80). The origin of the URL http://www.google.com/about consists of the three values http, www.google.com, and 80.

This means that the origin in the form mentioned in the preceding paragraph is the same for all websites on a server. Security issues ensue for web-hosting forms, which host all users under one domain, for example, Google’s free service sites.google.com. Because the different homepages are all in the same directory, http://sites.google.com/site, different users have access to the same localStorage. The specification suggests that in such environments, localStorage should not be used.

8.1.4 The “storage” Event

Every data change in the storage fires a storage event. The storage event offers read access to the key, the value before and after the change, the script’s URL that caused the change, and a reference to the storage object where the change was made.

The implementation of the storage event in current browsers can only be described as rather experimental. In Firefox 3.6, for example, the event is fired, but it does not contain the expected values. In Firefox 4 Beta 3, the event handler function was not started. Internet Explorer 8 does not know the standard call for attaching an event handler, window.addEventListener; instead, you have to use window.onstorage. The expected event then has to be read from the global window.event. The third Beta version of Internet Explorer 9 did not react to either event handler. Even Safari 5 did not show results for the storage event. Only Opera (version 10.60) and Google Chrome (version 6) returned the expected data for the storage event.

8.1.5 Debugging

While developing a web application, being able to see the current content of the persistent storage is very helpful. It is possible to fetch individual elements via getItem() and display them in an alert() window, but sometimes you just want to see the items listed as a simple table. Different browsers offer different options.

Firefox does not have its own graphic interface for displaying storage content; you need to use a free add-on. Firebug has been renowned among web developers for years as an indispensable extension of the Firefox browser and naturally also masters localStorage and sessionStorage. To look at the storage, you only need to enter the word localStorage or sessionStorage in the console, and the JavaScript object appears, containing the current values of the storage (see Figure 8.2). If you want to see the storage content without the Firebug add-on, you can also use the internal information in Firefox. The data is saved in the background in a SQLite database (version 3), which can be displayed with the command-line tool sqlite3. A graphic interface for SQLite is also available as a Firefox add-on: the brilliant sqlite-manager. The SQLite database file is in the Firefox profile directory and has the name webappsstore.sqlite.

Figure 8.2 The Firefox add-on Firebug displaying “sessionStorage”

image


Note

image

You can download the Firefox add-ons from these Internet addresses:

Firebug: http://getfirebug.com

sqlite-manager: http://code.google.com/p/sqlite-manager


Apple’s Safari offers an integrated debugging option, which first needs to be enabled in the Advanced Preferences. After activation, Safari shows a new Develop menu with a console that can display the storage content, just like Firebug.

Google Chrome and Opera also have integrated developer tools, allowing for very convenient access to all website elements. In both browsers, the Storage menu offers a clear and detailed list of localStorage, sessionStorage, and Cookies. In the table you can also add values, change them, or delete them (see Figure 8.3).

Figure 8.3 Opera displaying Developer Tools

image

Even Internet Explorer 9 offers Developer Tools. Apart from the DOM tree, CSS properties, a script debugger, and network profiling, there is a browser console that works in a similar way to that in Firebug, Safari, Chrome, and Opera.


Note

image

The latest browser versions really excel regarding the functions of their developer tools. Not only can cookies, sessionStorage, and localStorage be scrutinized, but these tools are also a big help in many other areas of web development.


8.2 Offline Web Applications

To make applications run completely without network access, HTML, JavaScript, and multimedia files must be reliably saved on the client machine. Up until now, all browsers had certain functions for caching content, but there was no standardized access to this content. The HTML5 specification took this problem to heart and devoted a section to Offline Web applications. They agreed on an independent offline memory, controllable with a simple configuration. A file with the ending .appcache contains the elements to be saved in the offline memory. It is integrated in the html tag as a manifest attribute:

<!DOCTYPE html>
 <html manifest="menu.appcache">
  <head>

The content of a cache manifest file can look like this:

CACHE MANIFEST
menu.html
menu.js
menu_data.js

The file structure is very simple. It does not have an XML structure, or syntax as you know it from the Windows .ini files, but is a simple text file. In the simplest case, all items listed in the file are transferred to the offline memory and only updated when the .appcache file changes. Every file referencing the manifest with the html element is automatically cached, even though the specification suggests listing the file once more explicitly. Let’s have a closer look at this configuration file.

8.2.1 The Cache Manifest File

The cache manifest file must be a text file encoded with the character set UTF-8, and the first line must contain the string CACHE MANIFEST. The web server also has to use the MIME type text/cache-manifest when it outputs the file.

If required, three special keywords can appear in the .appcache file, each introducing a separate section. Here is a quick example:

CACHE MANIFEST
menu.html
menu.js

# login requires network connection
NETWORK:
login.php

FALLBACK:
/ /menu.html

CACHE:
style/innbar.css

After the already familiar beginning of the file is a comment line starting with the symbol #. The string NETWORK: marks the beginning of a new section. Data in this section is put on a whitelist and always has to be fetched from the network. In the preceding example, it is the file login.php, because we want the login check in our example to be possible only online.

The FALLBACK section is applied if the browser is offline and the desired item cannot be accessed because it is not present in the offline cache. In this example, the desired item is defined with the lowest level of the web server (/) and therefore applies to all files on this server simultaneously. Instead of an inaccessible resource, we want to display the file menu.html.

Finally, the configuration file also contains the entry CACHE:, introducing another section of content to be saved. In this example, the stylesheet style/innbar.css could just as well be listed at the very top of the configuration file, and we could omit the CACHE section altogether.

The specification describes an interesting special case in which the cache manifest file contains the following:

CACHE MANIFEST
FALLBACK:
/ /offline.html
NETWORK:
*

With this trick, you can construct something like a complete offline cache of HTML pages on a web server: Each file referencing the cache manifest is saved locally when first loaded and only fetched from the server if the manifest changes. The FALLBACK section redirects all queries about HTML pages not found in the cache to the page /offline.html. The section NETWORK with the joker symbol (*) is required to display the page correctly even if the browser is online.

8.2.2 Offline Status and Events

Via the application programming interface (API) for Offline Web applications, web developers have the option of checking the status of the offline storage and can change it manually if necessary. The status queries refer to the constant status assigned to the object window.applicationCache. Its numeric content relates to the meanings presented in Table 8.2.

Table 8.2 Meaning of constants for application cache status

image

image

To retrieve the current values of the constant, you just need to enter its name in the browser console: window.applicationCache.status. This outputs the corresponding numeric value, similar to Figure 8.2. To be able to control application cache behavior, the browser triggers certain events, which can be retrieved in the programming:

window.applicationCache.addEventListener("progress",
    function(e) {
      alert("New file downloaded");
    }, false);

The progress event, for example, is fired for each newly loaded file. In that case, an alert window appears for every downloaded file. Table 8.3 shows a list of all events.

Table 8.3 The events for “Offline Web applications”

image

The error event can be especially useful when trying to locate errors. A file listed in the cache manifest that cannot be found fires this event in the browser. The browser aborts any script execution from this point on, a situation in which you probably would not think of first during debugging. More on debugging can be found in section 8.2.3, Debugging.

The JavaScript API offers two additional methods for the cache: update() and swapCache(). With these methods you can update the cache without reloading the page, for example, via an Update button. The following HTML fragment creates the button:

<button onclick="window.applicationCache.update();">
  update applicationCache</button>

We handle the updateready event in the JavaScript code:

window.applicationCache.addEventListener("updateready",
    function(e) {
      window.applicationCache.swapCache();
      alert("New Cache in action");
    }, false);

As soon as the update has been successfully downloaded, the function swapCache() overwrites the old cache with the updated version. The update function first checks the cache manifest file. If it has not changed, no update takes place regardless of whether individual files for the cache have changed or not. The same result as clicking the button with the mouse can be achieved by reloading the page.

There can be situations in which manual or automatic control of the cache can be appropriate, for example, for a monitor without user interaction displaying current news in a public space. The cache can be updated in the background via a continuously repeating function (setInterval()). The system can then display HTML pages reliably with or without network access.

The specification prescribes another attribute that indicates whether the browser is online or offline. window.navigator.onLine is meant to return the value false if the browser is set to not access the network or is sure that network access will fail. In all other cases, the variable returns true.


Note

image

Even if the value of window.navigator.onLine is true, this does not automatically mean that the browser has access to the Internet. The browser can also be online in private networks without necessarily being connected to the public Internet.


Modern browsers have a function for changing to offline mode. In Mozilla Firefox, for example, this function can be found in the File menu as Work Offline. If the browser changes from online to offline mode, the event offline is fired; vice versa, the event is online is fired:

window.addEventListener("online", function() {
 alert("You are now online");
}, false);
window.addEventListener("offline", function() {
 alert("You are now OFFLINE");
}, false);

This brief example creates an alert window as soon as the browser changes its online state. Offline-capable applications can use these events to load updated data from the server or copy locally saved data to the server.

8.2.3 Debugging

You have probably had the same problem as many web developers at one stage or another: You spend ages changing line by line of source code, but although the page is reloaded in the browser each time, the result remains unchanged. On the way from server to browser are many places where the web content can be stored temporarily. This is a desirable improvement in many cases and helps to conserve bandwidth but is also the cause of many lost hours of sleep for web developers.

The bad news is that Offline Web applications make this problem even more complicated. By adding an additional cache component, there are now even more places where elements can be updated or not updated. A structured approach to solving this problem is essential and can save you a lot of time.

You first need to ensure that the web server really does output the cache manifest in the current version. Look at the server log files, as in this example of the Apache web server:

::1 - - [26/Jul/2010:14:50:46 +0200] "GET
/code/chap_storage/menu.appcache HTTP/1.1" 200 491
"-" "Mozilla/5.0 (X11; U; Linux x86_64; en-US)
AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.0
Safari/534.3"
::1 - - [26/Jul/2010:14:50:46 +0200] "GET
/code/chap_storage/menu.appcache HTTP/1.1" 304 253
"-" "Mozilla/5.0 (X11; U; Linux x86_64; en-US)
AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.0
Safari/534.3"

The HTTP status code 200 means that the file was fully processed; 304, however, means that the file remains unmodified and is not reprocessed.

The next debugging options are integrated in the browsers. Here, the status is different in each browser; the most convenient tools can currently be found in Firefox and Google Chrome.

Google Chrome tracks the current state of the applicationCache object in the Developer Tools console. Figure 8.4 shows a first call of the page during which the browser creates the offline storage. Then, all related documents are downloaded, with the progress event being fired each time (see also, section 8.2.2, Offline Status and Events). Reloading the page creates the noupdate event because the cache manifest file has not been modified. Chrome lists the events very clearly in order.

Figure 8.4 Google Chrome status messages for offline storage

image

The developers of Mozilla Firefox integrated information about the cache directly in the browser. Under the address about:cache?device=offline, Firefox displays all elements in this cache as a list. If the browser is in offline mode, you can get even more detailed info on each element, such as the location of the file on the hard disk (see Figure 8.5).

Figure 8.5 Firefox information on an element in the offline cache

image

For the browser to reload the cache manifest, its content first needs to be modified. It is not enough to resave the file with the same content or update the date of the last modification with the UNIX command touch. When developing applications, this leads to developers adding a character in a commented out line only to then delete it again for a repeated reload request—a situation justifiable during developing, but the question remains how this could be automated in a productive environment.

If you use version control, such as Subversion, for your web applications, you may have just thought of keywords, such as ID or revision, which Subversion automatically replaces in case of a modification. But such a keyword is also only changed in the cache manifest file if its content has changed—so that’s another dead end. One possible aid would be a script that reads the version of the directory when distributing the new application version and writes it into the cache manifest. The prerequisite would be that all contents in the directory belong to the cache. A shell command for UNIX could look like this:

SVNV=$(svnversion -n) &&
 sed -e "s/^## svn.*/## svn repo version $SVNV/"
 -i menu.appcache

It replaces an existing comment line with the Subversion version of the current directory.

8.3 Browser Support

Support for Web Storage is present in all current browsers. Even Internet Explorer offers this function in version 8 and later. If you need to support older versions of Internet Explorer, you can use an Open Source JavaScript library for sessionStorage; it emulates the session storage using a trick. For further information and the download, see http://code.google.com/p/sessionstorage.

Unfortunately, Internet Explorer does not have any support for offline applications. Even in the upcoming version 9 these functions are not provided. Table 8.4 offers an overview of browser versions implementing Web Storage and offline apps. To see the connection between browser version and date, look at the Timeline at the end of the Introduction chapter, or go to the companion website at http://html5.komplett.cc/code/chap_intro/timeline.html?lang=en.

Table 8.4 Web Storage and offline web applications support in different browsers

image

8.4 Example: Click to tick!

To finish the chapter, we will use an example to illustrate the two techniques introduced here in combination. Click to tick! is a learning game that finds places or other geographical features using an unlabeled map. On this map, the player tries to mark a target as accurately as possible by clicking on the map with the mouse. The more hits per round, the more points the player will score in the final scoring.

To allow children to play the game on the iPad during a long car journey, the required resources, such as images, JavaScript, and HTML files, are saved in the cache for offline use (see section 8.2, Offline Web Applications). The list with top scores is saved in localStorage (section 8.1, Storage) to ensure that this information is not lost, even when the computer is switched off. Once the computer can reconnect to the Internet, the new score can be uploaded to the server, a function discussed in section 8.4.4, Expansion Options. Via an interface, the browser also checks if there are new game objectives and downloads these to the computer if necessary.

By applying the new techniques associated with HTML5, we created an independent program that uses the browser as a kind of runtime environment. Hardware as well as software and the operating system of the device become secondary; the browser is the central component for executing the program. Modern operating systems, such as Google’s ChromeOS or Palm’s webOS, count on this technique. By using the offline storage and the .appcache file, the program is equipped with an automatic update function—a true joy for developers.

Figure 8.6 shows the game in action: In this round, six out of eight places were successfully located in downtown Paris. Not bad!

Figure 8.6 The program “Click to tick!” during a game

image

8.4.1 Using the Application: As a Player

When starting the application, the browser loads the playing area on the left with an interactive map section containing the targets to be located. On the right, the browser displays a selection list with the available games and the current score. If the user has played the game before, he or she can also see the maximum percentage scored and the number of games played. As you can see in Figure 8.6, the game also shows whether the browser is currently connected to the Internet or not. If Internet access is possible, a button for updating the game is visible (Check for new games!).

In the course of the game, the user is asked to find specific places and can click on the map to guess the location of the desired place. In response to each mouse click, a little flag appears on the map, marking the location the user clicked on. At the same time, a circle marks the correct target location. The flag and the circle have the same color and are drawn transparently onto the map. If both symbols overlap, the target was correctly marked and the task was solved. The target is added to the list of targets found and ticked off with a check mark for a correct answer or an X symbol for an incorrect answer. This list also uses the same colors as on the map (refer to Figure 8.6).

Once all questions have been answered, the program rewards the user with praise or encourages the user to do better next time. The comments are based on the percentage of correct answers scored. The user can then choose a new game from the selection menu or reload the page to try the current game again.

8.4.2 Using the Application: As an Administrator

As mentioned previously, the game has a mode where you can define new targets (click2tick_creator.html). This admin interface loads the familiar map view of Google Maps and allows you to set the zoom factor, choose a map section, and then define several points on the map. Before you can start placing the points, you need to fixate the map section via the Record button. For each point, a line of JavaScript code is displayed on the right side of the browser, listing the coordinates in pixels and an identifier for the relevant point.

This part of the page is declared as contenteditable, so the identifiers can be modified directly in the HTML page (see Figure 8.7, the bordered area on the right). After all points have been marked and the identifiers adapted, the administrator has to copy the JavaScript code and save it in a JavaScript file, which is then referenced in the game’s HTML code in the head area. More details on how this works can be found in the introductory text of the admin site.

Figure 8.7 Creating a new game using the administration interface

image

The last step for making the new game offline-capable is to enter the created JavaScript file into the appcache file. To find the correct address for the static Google Maps map, you need to call the game once with the debug option. This is easily done by adding the following string to the URL of the administration site: ?debug=1. In this mode, the URL of the active image is displayed below the playing area.


Note

image

If you are interested in finding out more about the fascinating interplay between Google Maps API, Canvas, and JavaScript in the administration interface, look at the source code at http://html5.komplett.cc/code/chap_storage/click2tick_creator.html.


8.4.3 Important Code Fragments

The following sections explain the most important parts of the Click to tick! game. We begin with the HTML code, move on to the manifest instructions, and finally work out the JavaScript part.

8.4.3.1 The HTML Code for the Game

The HTML code for the game Click to tick! is rather clear. Less than 50 lines of well-formatted code form the basic structure of the application, as shown in Listing 8.1. Of course, the application logic resides not in the HTML code, but in the approximately 300-line long JavaScript file. It is primarily the placeholders for the elements to be filled that are encoded in HTML:

Listing 8.1 Extract of the HTML code for the game “Click to tick!”


<!DOCTYPE html>
 <html manifest=click2tick.appcache>
  <head>
    <meta charset="utf-8">
    <title>Click to tick!</title>
    <link rel="stylesheet" media="all"
      href="click2tick.css">
  <script src="click2tick.js"></script>
  <script src="click2tick_game0001.js"></script>
  <script src="click2tick_game0002.js"></script>
 ...
 <div id="map">
  <fieldset>
    <legend>Map</legend>
    <canvas>This game requires a canvas capable browser/canvas>
  </fieldset>
  <p id=mapUrl></p>
 </div>
 <div id="controls">
  <fieldset>
    <legend>Questions</legend>
    <p>Choose a game:
      <select id=selGame name=games></select></p>
    <ul id="gameResults"></ul>
    <h3 id="curQuestion"></h3>
  </fieldset>
  <fieldset>
    <legend>Status</legend>
    <p>You are <span id="onlineStatus" class=online></span></p>
    <p id="localStorage"></p>
    <p id="updateButton"><input type=button onclick="location.reload();"
      value="Check for new games!"></p>
  </fieldset>
 </div>


The listing starts with the familiar DOCTYPE definition and the subsequent reference to the appcache file where the content to be saved is referenced. For each game, a dedicated JavaScript file is loaded—here, for example, the file named click2tick_game0001.js.

In the second part of the listing you see a canvas element, a strong indication that this is the interactive playing area. The select element with the ID selGame is still empty but will contain the list of all active games when the game is started. The other HTML elements with the IDs gameResults, curQuestion, onlineStatus, and localStorage are placeholders, which will also later be filled by JavaScript functions. The button labeled Check for new games! reloads the website via location.reload and automatically checks if the manifest file has been modified.

8.4.3.2 The Manifest File

After the obligatory first line, the cache manifest contains references to the HTML code, the JavaScript file, and the stylesheet. Then, the corresponding JavaScript file and the static map of Google Maps are referenced for each game:

CACHE MANIFEST

# application files
click2tick.html
click2tick.js
click2tick.css

# gamedata
# Downtown Paris
click2tick_game0001.js
http://maps.google.com/maps/api/staticmap?sensor=false&maptype=satellite
&size=640x480&center=48.864721,2.3105226&zoom=14

Although the map call for the Google Maps map consists of a dynamic URL, the resulting image is saved in offline storage and correctly loaded even without network access if called. Figure 8.8 shows successful loading of three games into the application cache.

Figure 8.8 Google Chrome Developer Tools loading the offline cache

image

8.4.3.3 JavaScript Functions of the Game

The HTML part of our example was not very exciting, but the JavaScript part of the game is much more interesting. The previously mentioned window.onload function initializes a new object game with the type click2tick and then calls the init function of this object:

window.onload = function() {
  var game = new click2tick();
  game.init();
};

To keep the JavaScript code as flexible as possible, the entire game function is wrapped in a library (GameLib), made accessible with all functions as global object:

(function () {
  var GameLib = function () {
    var elem = {};
    var image, canvas, context;
 ...
  };
  // expose object
  window.click2tick = GameLib;
}());

In the second-to-last line of the listing, the window object is assigned the GameLib class with the new name click2tick. The init function of this class loads the existing games, initializes the canvas element, and starts the first game (see Listing 8.2):

Listing 8.2 The init function for the GameLib library


    this.init = function() {
      // build game-selection pulldown
      var o = ''
      for (var i=0; i<gamedata.length; i++) {
         o += addOpt(i,gamedata[i].title);
      }
      _get('selGame').innerHTML = o;
      _get('selGame').options.selectedIndex = 0;
      _get('selGame').onchange = function() {
        startGame(this.value);
      };

      // define empty image used for map later
      image = new Image();

      canvas = document.querySelector("CANVAS");
      context = canvas.getContext('2d'),
      canvas.onclick = function(evt) {
        checkPosition(evt);
      };
...
      startGame(0);
    };


The functions addOpt() and _get() will probably be new to you. These are two auxiliary functions; addOpt() serves to assemble the string for a new option element, and _get() allows efficient access to the elements in the DOM tree (via their ID). The HTML element with the ID selGame is the selection list of all games. This list is set to the first element with selectedIndex = 0. If another item in this list is selected, the startGame function is activated with this new value.

An event handler for the mouse click event is assigned to the canvas element and calls the checkPosition function. Then the first game is started.

Because many functions of the GameLib are designed to ensure that the game runs properly and are not directly connected with Web Storage or the offline cache, we will not describe them in great detail here. If you are curious, you can peek at the source code for the JavaScript library click2tick.js. But more relevant for our offline chapter is the JavaScript code involving the Storage interface with localStorage: We will use it when saving a game:

// store basic data in localStorage, add hostname
// and timestamp
var ts = new Date().getTime();
var id = "click2tick_"+game.store.gid+"_"+ts;
game.store.hostname = location.hostname;
game.store.ts = ts;
localStorage.setItem(id, JSON.stringify(game.store));

To ensure the keys in localStorage are unique, they are created by combining a prefix string (click2tick), a game ID (game.store.gid), and a timestamp (ts) connected by an underline (_).

The game.store structure is saved as a value with all results in the form of a JSON string. The following listing shows the value after five out of eight questions have been correctly answered at the end of the game. The key for the following entry is click2tick_0001_1281026695083 (see also Figure 8.8):

{ "gid":"0001","game":"Downtown Paris",
  "questions":8,"correct":5,"percent":63,
  "hostname":"html5.komplett.cc", "ts":1281026695083
 }

The timestamp ts is the time in milliseconds since 1.1.1970. Now that the values are saved in localStorage, you can give appropriate feedback to the user if the user tries the game again:

// get collected data
var games_done = [];
var max_percent = 0;
for (var i=0;i<localStorage.length;i++) {
  var key = localStorage.key(i);
  if (key.substring(0, 9) == "click2tick") {
    var item = JSON.parse(localStorage.getItem(key));
    if (item.gid == game.store.gid) {
      games_done.push(item);
      max_percent = Math.max(max_percent, item.percent);
    }
  }
}

// show collected data
var s = '';
if (games_done.length == 0) {
  s += 'You have not played this game before.';
}
else {
  s += 'You have played this game '+
    (games_done.length+1)+' times<br>';
  s += 'Your best hit rate till now: '+
    max_percent+"% ";
}
_get('localStorage').innerHTML = s;

The for loop runs over all items found in localStorage. For each element, the key is determined and checked if it starts with the string click2tick. This check ensures that any elements saved by another application of this website are skipped in localStorage.

We then use the JSON.parse function to convert valid elements into JavaScript objects. If the game ID matches the ID of the current game (item.gid == game.store.gid), the object is added to the array games_done and checked if its hit rate is higher than the highest previous one (Math.max). A string s is then assembled, giving details on the number of games played and the maximum percentage. As you can see in Figure 8.5, the game also indicates whether the browser is online or offline. This is relevant for our application because the player cannot look for new games and updates while in offline mode:

var setOnlineStatus = function() {
  if (navigator.onLine) {
    _get('onlineStatus').innerHTML = 'Online';
    _get('onlineStatus').className = 'online';
    _get('updateButton').style.visibility = 'visible';
  }
  else {
    _get('onlineStatus').innerHTML = 'Offline';
    _get('onlineStatus').className = 'offline';
    _get('updateButton').style.visibility = 'hidden';
  }
}

A check of the variable navigator.online (see section 8.2.2, Offline Status and Events) decides if the button for updating the application will be displayed. To ensure the online status is always up-to-date, event listeners are defined for both switching to and from offline mode:

// control online-status
window.addEventListener("online", function() {
  setOnlineStatus();
}, false);
window.addEventListener("offline", function() {
  setOnlineStatus();
}, false);

8.4.4 Expansion Options

To make the game more attractive, you could try adding the following optional expansions:

Select difficulty level. The valid area of objects is defined in pixels in the image. The default setting of 15 pixels is suitable for average difficulty. You could make this area variable via an input field using the new HTML5 form element range. The level of difficulty would of course have to be taken into account in the high score list.

Incorporate variable sizes/shapes of target. Because the objects to be located often have different sizes, an additional parameter for each target object might be conceivable, specifying the radius of the area to be searched. If a circle is not accurate enough as a target object, you could integrate other geometric forms as targets.

Include score by distance. You could incorporate the distance to the target into the scoring: the closer a player’s mouse click was to the desired target on the map, the higher the score the player gets for being right.

Add online high score. An extension in connection to offlineStorage would be integrating the application into an online high score list. You would require an application with access to the database on the web server.

Combine with Geolocation API. This expansion takes our example a bit further: After locating the player’s current position with sufficient accuracy (see Chapter 7, Geolocation), a corresponding Google Maps map section could be loaded. The player’s task would then be to find his own location on the map as accurately as possible. In this variation, the game is no longer offline-capable but is definitely suitable for mobile devices.

Summary

In this chapter you encountered two different types of client-side storage: web storage, a structured storage for reading and writing web applications, and offline storage, temporarily saving entire web applications or parts of them on the client side.

The chapter concluded with a programming example, the game Click to tick!, demonstrating the strengths of the offline cache and localStorage. By using the two new techniques, we created an application that could run on the web browser but was still fully functional without Internet access. The automatic update function was the icing on the cake. The user did not have to worry about installation, nor did the user need administrator rights.

..................Content has been hidden....................

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