Other HTML5 APIs - Mobile HTML5 (2013)

Mobile HTML5 (2013)

Chapter 6. Other HTML5 APIs

Offline Web Applications

Until now, users of web applications have only been able to use applications while connected to the Internet. When offline, web-based email, calendars, and other online tools have been unavailable, and for the most part, continue to be.

While offline, users may still access some portions of sites they have visited by accessing what is in the browser cache, but that is limited and difficult to manage. If a user gets bumped offline in the middle of a process, like writing an email or filling in a form, hitting submit can lead to a loss of all the data entered.

The HTML5 specification provides a few solutions, including local and session storage for storing data locally, and an offline application HTTP cache for ensuring applications are available even when the user is offline. HTML5 contains several features that address the challenge of building web applications that don’t lose all functionality while offline, including indexDB, offline application caching APIs, connection events, status, as well as the localStorage and sessionStorage APIs.

Am I Even Connected to the Internet?

One thing you may want to know when implementing offline features is if the user is indeed connected to the Internet. HTML5 defines an onLine property on the Navigator object so you can determine whether the user is currently online:

var isOnline = navigator.onLine;

This will return true or false. Note that if it returns true, it could mean the user is on an intranet and does not necessarily mean the user has access to the Internet.

Application Cache

If you want to create web-based games that are able to compete with native games on mobile devices, you have to ensure that players can access your game even when they are not online. We want CubeeDoo players to be able to play whether they’re at home on their WiFi, camping in the Mojave desert (why enjoy nature when you could be flipping cards?), or even flying over the Pacific. Application cache enables you to create web-based applications that are accessible even when the user is not currently online.

In the past, desktop browsers have only been able to save the HTML file and associated media to a local folder. This method works for static content, but it never updates and is generally a bad user experience.

With the ubiquity of web-based applications, it is more important than ever that web applications are accessible when the user is offline. While browsers have been able to cache the components of a website, HTML5 addresses some of the difficulties of being offline with the application cache (“AppCache”) API.

AppCache allows you to specify which files should be cached and made available offline, enabling your website to work correctly when your user is offline even if they reload a page. Using the AppCache interface gives your web application the following advantages: (1) offline browsing, (2) faster reloads, and (3) reduced server load. With application cache offline browsing, your entire site can be navigable even when a user is offline.

AppCache on most mobile browsers enables the local storage of up to 5 MB (or limits you to 5 MB, depending on your perspective). Different browsers may have different limits. While users can change these limits in their browser preferences, you should always code to browser default values, unless all your users are power users.

For AppCache to work, you must include the manifest attribute in the opening <html> tag, the value of which is the URL of a text file listing which resources should be cached. In your HTML file, include manifest="URL_of_manifest":

<!doctype HTML>

<html manifest="cubeedoo.appcache">

<meta charset="utf-8"/>

<title>....

With the inclusion of the manifest attribute on the <html> element linking to a valid manifest file, when a user downloads this page, the browser will cache the files listed in the manifest file, the manifest file itself, and the current document, making them available even when the user is offline. Even though the current document is by default cached, it is best to list it among the cached files in the manifest.

NOTE

The document linking to the manifest file is cached by default.

So how does it work? When a browser sees a manifest attribute, it downloads the manifest file and attempts to cache the files listed in that manifest. If the user opens a locally stored website when offline, it uses the already cached files. If online, the browser accesses the cached site first, and only then does it check to see whether updates have been made to the cache manifest file and therefore the cache.

If changes have been made to the cache manifest file, the browser will download the entire cache before making updates to the cache in memory. The browser looks for changes in the manifest file, not the rest of the files on the server, to determine whether the cache should be refreshed. In other words, to get the cache to update, you need to edit the manifest file itself. Updating your other assets is not enough. Remember this when the “comment” is detailed in the section Updating the cache.

After loading the site from the cache, it fetches the manifest file from the server. If the manifest has changed since the page was last visited, the browser re-downloads all the assets and re-caches them. If the browser fails to re-download all of the assets, it continues to use the old cache. However, if the browser successfully downloads all the required files, it still continues to use the old cache, switching to the newer cache the next time the user accesses the site.

The cache manifest file

The .appcache file is a text file that lists the resources the browser should cache to enable offline access to your application. The file must start with the following string: CACHE MANIFEST. The required string is then followed by a list of files to be cached, and optional comments and section headers.

Your .appcache file should be served with the MIME-type text/cache-manifest. Add:

AddType text/cache-manifest .appcache

to your .htaccess file or server configuration. This used to be required, but is now optional. The manifest file is permanently stored in the browser cache. An .appcache may look something like this:

CACHE MANIFEST

#version01

#files that explicitly cached

CACHE:

index.html

css/styles.css

scripts/application.js

#Resources requiring connectivity

NETWORK:

signin.php

dosomething.cgi

FALLBACK:

/ 404.html

Note that the files listed in the cache manifest file are relative to the manifest file.

To create a comment, include a # as the first character of the line and the remainder of the line will be ignored.

Section headers add more control to how AppCache treats your web files and assets. There are four possible section headers: CACHE, FALLBACK, SETTINGS, and NETWORK, each followed by a colon.

The files following the CACHE header are explicitly cached. If no header is defined, or if files are listed above the headers, those files are cached as if they followed the CACHE header. Note that secure (HTTPS) files can only be cached if from the same origin as the manifest.

The file containing the cache manifest file in the <html> element is always added to the cache whether or not it’s listed under the CACHE header.

Do not list the manifest file itself, or the site may never update.

The files following the NETWORK heading are explicitly not cached, and are therefore only accessible when the user is online.

FALLBACK files include paired files: files to show, and fallback files to backfill if the former file is not available. (If the first file in the pair is not available, the second file listed on the line will be served.) If included, the SETTINGS header should be last and list the single line value prefer-online.

WARNING

Do not list the cache.appcache file as a file to be cached in your manifest file, or your site may never update.

Updating the cache

The browser cache is not updated or overwritten until a change is made to the manifest file or by using applicationCache JavaScript methods. Making an update to a file listed in the manifest, such as your JavaScript, CSS, or HTML, is not sufficient: a change needs to occur in the manifest file itself.

A standard practice is to add a comment within the manifest file to force a file update. In the preceding snippet, changing the comment #version01 to #version02 will inform the browser that the cache should be updated. Using a timestamp instead of a version number may be more intuitive for you. Note that what you put in the comment is not important, only that it creates a change in the file that the browser will detect.

The version number for our cache is basically the comment on the second line. When the user requests that web page, the browser first loads the site from the cache if the cache is present. Then it downloads the .appcache file from the server and compares it with the one in memory. If there is a change to the manifest file—such as a change in that version number—it will download the rest of the cache. This is why we add a comment. It is much easier to change a comment than to change a filename (and all the files linking to it). Changing the comment with a version number or timestamp has become the standard way of informing the browser that the manifest file should be considered updated.

Once an application is offline it remains cached until the user clears their browser’s data storage for your site, the .appcache file is modified, or the application cache is programmatically updated.

The browser first loads the site from the cache. Only then does it check to see if there are changes in the manifest file. When a change is noted, all the files listed in the manifest file are downloaded. The cache is not updated, however, until all the files are successfully retrieved from the server. If the manifest file or a resource specified in it fails to download, the entire cache update process fails—there is no risk of your user seeing a partially updated version of your web application.

You can force an update to the cache without altering the manifest file programmatically. To explicitly update the cache, call applicationCache.update(). When the status is ready, swap the old cache for the new one:

var appCache = window.applicationCache;

if (appCache.status == appCache.UPDATEREADY) {

appCache.swapCache();

}

The possible status values include UNCACHED, IDLE, CHECKING, DOWNLOADING, UPDATEREADY, and OBSOLETE. If the manifest file or a resource specified in it fails to download, the entire cache update process fails and the browser will keep using the old application cache.

While these steps update the cache, they do not update what the user is currently viewing. The user will continue viewing the previously cached version of the site until the next time he or she tries to access the web application. It thus takes two loads of your site for the user to get the new content.

You can force the new site on the user by reloading the site based on an updateready event handler, but having the site reload while the user is interacting with it could be bad user experience; do so thoughtfully.

In terms of CubeeDoo, we can add all of the files to the manifest file. We would have excluded the secure login form if we had one. We also include a comment with the version number (or the date, or something that makes sense to you), which we will update if we ever make changes to any of the files listed under the CACHE: or FALLBACK: headers:

CACHE MANIFEST

#version01

CACHE:

index.html

css/cubeedoo.css

scripts/cubeedoo.js

assets/matched.mp3

assets/notmatched.mp3

images/shapes.svg

NETWORK:

login.html

FALLBACK:

/ 404.html

When we are ready to deploy our application, we will add the manifest attribute to our index page’s <html> tag. But don’t do it now. There is very little that is more annoying than developing and testing a web application that is completely cached in the browser:

<html lang="en-us" manifest="cubeedoo.appcache">

Browsers will also clear the cache if they are unable to find the cache manifest file on the server. Linking to a nonexistent file, thereby returning a 404 Not Found response, will cause the browser to clear the cache.

Local and Session Storage

With application cache, we can get our web applications saved onto devices so they’re available offline. Application cache enables you to store the files, but sometimes you also need to store data. For example, when our CubeeDoo player is offline (and online), we want to maintain our high scores with the names, times, and scores of these overachievers along with the current game state when a player pauses the game. For these features, we could use localStorage, IndexedDB, or the deprecated Web SQL Database.

We’re going to use localStorage to pause the game and use the deprecated (and yet still pervasive) Web SQL Database to save high scores. We could have done the inverse. We can’t, however, use IndexedDB, since it is not yet supported on iOS or Android (though IE10, Blink, and Firefox added support in recent releases).

LocalStorage and sessionStorage are easy-to-use key/value stores. You may be thinking “but we have cookies, so what is the big whoop-dee-doo?” There are a lot of drawbacks to cookies, which localStorage solves.

The cookie comparison

The main uses for cookies are session management, personalization, and tracking. Server cookies are strings sent from the web server to the browser and back again with each HTTP request and response. The browser can return an unchanged cookie to the server, introducing state into an otherwise stateless HTTP transaction. Client-side cookies are JavaScript-generated strings that can be used to enable state, pass information back and forth to the server, or even simply to maintain values client-side, such as items in a shopping cart.

Browsers can store 300–400 cookies with a maximum size of 4 KB per cookie, and a limit of 20 cookies per server or domain. All cookies are sent with each HTTP request. While this automatic sending of information may be used to your favor, one of the big downsides in terms of mobile is the increased bandwidth. Also, passing cookies back and forth can be a security risk.

Whereas cookies are limited to 20 cookies at 4 KB each for a total of 80 KB per domain, the new local and session storage standards allow for more space. The size depends on the browser, but is generally in the MB rather than KB range.

LocalStorage is used for long-term storage of lots of data for a particular domain within a single browser. LocalStorage data persists after the browser or browser window is closed. LocalStorage data is accessible across all browser windows.

SessionStorage data is confined to the browser window that it was created in, and gets deleted when the session ends. SessionStorage is accessible to any page from the same origin opened in that window. If you open a window and navigate from page to page in the same site in the same window, every page navigated to within that same browser window will have access to the sessionStorage. If the user has multiple windows opened—for example, viewing your site in three separate browser windows—each browser window would have its own individual copy of the sessionStorage, but would share the same localStorage key/value pairs.

Long-term and session cookies are both sent to the server with every HTTP request. If you need to send information to the server, cookies may be the right solution. However, saving state via cookies, like many of us did before HTML5, meant sending lots of useless information to and from the server, wasting bandwidth. LocalStorage and sessionStorage both save bandwidth.

SessionStorage and localStorage both have the same five methods and a single property, as shown in Table 6-1.

Table 6-1. SessionStorage and LocalStorage methods and properties

Method/property

Description

setItem(key, value)

Sets the value for the given key. For example, define the session variable with:

sessionStorage.setItem('keyname', 'data value')

localStorage.setItem('keyname', 'data value')

getItem(key)

Retrieves the value for the given key. Returns null if the key does not exist:

sessionStorage.getItem('keyname')

sessionStorage.keyname

localStorage.getItem('keyname')

localStorage.keyname

removeItem(key)

Removes the key and its associated value. Unset the value with:

sessionStorage.removeItem('keyname')

localStorage.removeItem('keyname')

clear()

Removes all key/value pairs. Clear all the key value pairs with:

sessionStorage.clear()

localStorage.clear()

key(position)

Returns the key for the value in the given numeric position:

sessionStorage.key(position)

localStorage.key(position)

length

The read-only length property indicates how many key/value pairs are currently stored in sessionStorage:

sessionStorage.length

localStorage.length

Using sessionStorage and localStorage is extremely easy. It is like a regular object with a predefined name: sessionStorage and localStorage, respectively.

There is an argument as to the speed or performance of these storage APIs. While hitting the hard drive to retrieve data is slower than hitting a JSON value in the browser, hitting the hard drive on a mobile device is generally more performant than making an HTTP request.

LocalStorage to enhance mobile performance

Some websites, like http://m.bing.com, have taken advantage of localStorage to reduce the number of HTTP requests a page load makes. As briefly described in <style> and mobile performance: standards anti-pattern, they include the scripts and styles in the first hit to the server, then extract the JavaScript and CSS into separate localStorage name/value pairs. Each script has a unique identifier as the name in the name/value pair, which is stored inside a cookie.

When the user makes a request for a new page, the cookie gets sent along with the request informing the server which files are already stored in the user’s browser. The server then only sends the needed files. This reduces a page request to a single HTTP request.

While the first request may have a large file size, subsequent requests are small as all of the assets are stored locally in localStorage. While including scripts and styles within a page is an anti-pattern of performance and standards, it has been used effectively to improve the performance on some mobile web applications and sites.[40]

Data and user settings persistence is not just helpful in terms of user experience, but can also benefit web users whose data or WiFi may not be consistent, be it due to overloaded cell towers, lack of data, or the user wanting to limit their data usage.

Because application cache isn’t the panacea we are all hoping for, developers have developed their own best practices for offline application storage, generally mixing application cache with localStorage. The Financial Times has a good article explaining their process, reasoning, and code.

CubeeDoo

In CubeeDoo, we use localStorage to save state for pausing the game, and sessionStorage to store the username and the game’s default values. You can use sessionStorage or localStorage for all three, or any combination of the two. I chose to use both to demonstrate both.

We are leveraging the storage APIs to reduce the need to save state server-side. In fact, there is no server backend for CubeeDoo. Our server only needs to store and serve static files. All of the features such as high scores that generally sit on a database in the cloud are on the user’s device.

LocalStorage is used to maintain state when the game is paused. When the user pauses the game, we use the custom data attributes and the dataset API to set and get the values and locations of each card. We store the card values in localStorage, along with all the other relevant data—such as time left, current level, current score, etc.—required to continue the game where and how we left off. Had we used sessionStorage, pausing the game would have worked just as well, but the information would have been cleared when the user closed the browser window, as sessionStorage key/value pairs are cleared when a browser session is terminated.

SessionStorage is used to temporarily store the user’s name. The benefit of using sessionStorage instead of localStorage for the username is that a separate player’s username can be maintained in a second tab in the same browser. The drawback (or benefit) is that when the user closes the browser, the username (used for listing and storing high scores) is cleared.

We stored the original state of the game—the default values—with sessionStorage. It’s employed to save the default game settings when the game is initially loaded. When the user progresses through the game, the levels increase, then the time allowed per level decreases, and so on. When the user starts a new game, instead of refreshing the page, we pull the original values out of sessionStorage. In this way, starting a new game does not require a page reload to access the game settings, which may have changed during the previous game. We could have saved these variables as properties on a global object in our script, but reloading would have reset the values to the default values set in our JavaScript.

I’ve chosen to store these default values in sessionStorage because, with HTML5, I can! I used sessionStorage instead of localStorage so those values don’t maintain state between sessions.

I’ve included the following functions (among many others):

storeValue

Stores default game values.

alterValue

Updates stored default values.

pauseGame

Pauses game. Stores current state in localStorage.

playGame

Resets the game to pre-paused state, putting the cards back in their place and restarting the timer.

reset

Clears localStorage set up when game was paused, clearing the saved paused state of the game.

Note qbdoo is the top-level namespace for CubeeDoo, and has several properties you can control:

1 var qbdoo = {

2 //game settings

3 currentLevel: 1,

4 currentTheme: "numbers",

5 gameDuration: 120,

6 score: 0,

7 matchedSound: 'assets/match.mp3',

8 failedMatchSound: 'assets/notmatch.mp3',

9 mute: true,

10 cardCount: 16,

11 iterations: 0,

12 iterationsPerLevel: 2,

13 possibleLevels: 3,

14 maxHighScores: 5, ...

You can set the default values such as the number of cards, iterations per level, initial duration of a round, and so on. You, as the developer, can alter any of these default values. As the user plays the game, some of these values get altered. We store the original values, and restore these values as they get altered in sessionStorage.

The storeValues() function stores the initial game values:

1 storeValues: function(newgame) {

2 var currentState = {};

3 //capture values for play

4 currentState.currentTheme = qbdoo.currentTheme;

5 currentState.timeLeft = qbdoo.timeLeft;

6 currentState.score = qbdoo.score;

7 currentState.cardCount = qbdoo.cardCount;

8 currentState.mute = qbdoo.mute;

9 currentState.iterations = qbdoo.iterations;

10

11 // get all the cards values and positions

12 // use dataset to get value for all the cards.

13 if (newgame == 'newgame') {

14 currentState.currentLevel = qbdoo.currentLevel;

15 currentState.score = 0;

16 currentState.gameDuration = qbdoo.gameDuration;

17 sessionStorage.setItem('defaultvalues',

JSON.stringify(currentState));

18 return;

19 } else {

20 return currentState;

21 }

22 },

The storeValues() function is called when the game is initialized to store the default values set in our JavaScript file. As the user plays the game, some of these values change. By storing these values, when the user starts a new game by clicking on the new button, we do not need to reload the page. Instead, the default values are captured in lines 4–9, and updated in 14–16 if the user is starting a new game (without reloading the page).

When the function initially called, we set the values on the locally scoped currentState object. In line 17, we use JSON’s stringify() method to turn that object into a JSON string. We then save that string in sessionStorage with the key defaultvalues using sessionStorage’ssetItem() method. We use the key defaultvalues to retrieve the value with the getItem() method, which we do in our playGame() function.

We’ve included an alterAValue() function to update or return the default values set with storeValues(), should a user choose to change settings or should the progression of the game change the user’s settings.

23 alterAValue: function(item, value) {

24 var currentState = JSON.parse(sessionStorage.getItem('defaultvalues'));

25 if (value) {

26 currentState[item] = value;

27 } else {

28 qbdoo[item] = currentState[item];

29 }

30 sessionStorage.setItem('defaultvalues', JSON.stringify(currentState));

31 return value;

32 },

The parameter of the alterAValue() function is the item to be set or retrieved and an optional value for the item, if the item is to be set. When the user changes the theme of the cards or mutes/unmutes the audio, the item and value are sent as arguments with the function call. ThealterAValue() function fetches the item from sessionStorage, alters the object property required, then re-saves the default values for the game in sessionStorage to reflect the new value.

The function retrieves the default setting from sessionStorage with the getItem() method in line 24. The return value is the JSON string we had stored in sessionStorage with the setItem() method earlier with the storeValues() function. Because we stored a JSON string, when we retrieve the value with the getItem() method, a JSON string is returned. We parse it with the JSON.parse() method to define our locally scoped currentState object.

If two values are passed to the alterAValue() function, the first parameter is the game property to be altered. The second parameter is the new value of that game property. The currentState object is updated to reflect that change. If only one parameter is passed, the alterAValue()function returns the value of that game property.

We’ve included pauseGame()and playGame() functions, to pause and play the game, along with associated functions:

33 pauseGame: function(newgame) {

34

35 var currentState = {}, i, cardinfo = [];

36 if (qbdoo.game.classList.contains('paused')) {

37 qbdoo.playGame();

38 return false;

39 }

40

41 qbdoo.pauseOrPlayBoard('pause');

42 currentState = qbdoo.storeValues();

43 currentState.currentLevel = qbdoo.currentLevel;

44

45 for (i = 0; i < qbdoo.cardCount; i++) {

46 cardinfo.push(qbdoo.cards[i].dataset);

47 }

48

49 currentState.cardPositions = JSON.stringify(cardinfo);

50 localStorage.setItem('pausedgame', JSON.stringify(currentState));

51

52 qbdoo.clearAll();

53

54 },

The pauseGame() function is called when the pause/play button is clicked in the upper righthand corner of our game, toggling between paused and play states. The current state of the game—whether paused or in play—is determined by the class on the game. In lines 36–38, we note that if the game is already paused, as indicated by the presence of the paused class, the playGame() function, described in the next section, is called. If the game is not already paused, we call the pauseOrPlayBoard() method in line 41 that toggles the game’s class and clears the timer interval.

To pause the game, we need to store the current state of the game. We need to store the remaining card face values and locations as well as the state of the game. We capture some of the stored state of the game values with a call to the storeValues() method in line 42, which we described earlier. We add the current level with the currentLevel property to the state object in line 43.

We then iterate through all the cards, pushing the dataset key/value pairs into the cardinfo array in lines 45–47. We turn that array into a JSON string, adding the card positions into the state object in line 49. We then stringify the entire currentState object and store the JSON string we’ve created in the browser as the pausedgame key’s value in line 50 in localStorage.

In the last line, we clear the board by calling the clearAll() method. That function clears the cards from view by changing the value of the custom data attribute data-value to 0 for all cards. We hide all of the cards that have a data-value value of 0. We discuss CSS selectors and how we target based on attributes in Chapter 7.

classList

Note that we used the term classList on line 36. The classList object, added to all nodes within the DOM, provides us with the ability to add, remove, toggle, and query the existence of classes on any DOM node. classList returns a token list of the class attribute of the element:

node.classList.add(class)

Adds the class to the node.

node.classList.remove(class)

Removes the class from the node’s list of classes if it was present. If the class was not present, it does not throw an error.

node.classList.toggle(class)

Adds the class to the node’s list of classes if the class was not already present, and removes it if it was.

node.classList.contains(class)

Returns a Boolean: true if the DOM node’s list of classes contains a specific class; false otherwise.

classList has been supported since iOS 5, Android 3, and IE10.

When the user pauses the game, the pause button becomes a play button. This change is done with CSS based on the paused class we added with the pauseGame() function. When the user then clicks on that same button again, the conditional in line 36 returns true, calling the playGame()function:

55 playGame: function(newgame) {

56 var cardsValues, cards, i, currentState = {};

57

58 if (newgame == 'newgame') {

59 currentState = JSON.parse(sessionStorage.getItem('defaultvalues'));

60 qbdoo.timeLeft = qbdoo.gameDuration = currentState.gameDuration;

61 } else {

62 // get state via local storage

63 currentState = JSON.parse(localStorage.getItem('pausedgame'));

64

65 if (qbdoo.game.classList.contains('paused')) {

66 qbdoo.game.classList.remove('paused');

67 }

68 qbdoo.timeLeft = currentState.timeLeft;

69 }

70 qbdoo.reset('pausedgame');

71

72 qbdoo.currentTheme = currentState.currentTheme;

73 qbdoo.mute = currentState.mute;

74 qbdoo.currentLevel = currentState.currentLevel;

75 qbdoo.score = currentState.score;

76 qbdoo.cardCount = currentState.cardCount;

77 qbdoo.iterations = currentState.iterations;

78

79 qbdoo.setupGame(currentState.cardPositions);

80 },

The playGame() function is called when the user restarts a game from pause and when the user starts a new game after losing a previous game. If the user is restarting the game, we want to continue the game from where we left off. If we want to start a new game, we want to reset the default values of the game. The playGame() function handles both.

If starting a new game, with lines 58–60, the function retrieves the default values for the game from sessionStorage with the getItem() method, parsing the string and assigning it to the currentState object. We also reset the gameDuration to its appropriate value, and change thetimeLeft to that value.

Otherwise, if the game is started from pause, we get the saved state with card positions and values from localStorage with the getItem() method, passing the pausedgame key instead of the defaultvalues key, which is a sessionStorage key, getting the pausedstate and parsing the JSON string into the currentState object. The function also changes the class of the board to drop the paused class.

The end of the function sets the game properties based either on the defaultvalues captured from sessionStorage or the paused values captured from localStorage, before calling the setupGame method to start the game.

In line 70, we call the reset() function, which deletes the paused game values stored in localStorage, using the removeItem() method, which has as its only argument the key name for the localStorage key/value pair we want to delete:

81 reset: function(item) {

82 localStorage.removeItem(item);

83 }

In CubeeDoo, when we pause the game, we use custom data attributes and a custom dataset to extract the card position and values, the JSON.stringify() method to turn the game object into a JSON string, and then store that string in localStorage.

In terms of sessionStorage, the important line here is line 30. We created a sessionStorage entry with a key of defaultvalues and a value of a JSON string representing our current state object. Lines 6 through 25 (in the earlier code snippet) create the properties of that object, with lines 18 through 25 using custom data attributes and the dataset API (covered in Chapter 2) to capture the key/value pairs showing each card’s position and value.

When we restart the game, we get the item from localStorage:

gameState = localStorage.getItem('cubeedoo');

And then we delete the stored state from memory with:

localStorage.removeItem('cubeedoo');

Items that are stored in localStorage and sessionStorage are visible in the various debuggers, as shown in Figure 6-1.

Contents of localStorage and sessionStorage are visible via the browser’s debugging/web inspector tools

Figure 6-1. Contents of localStorage and sessionStorage are visible via the browser’s debugging/web inspector tools

Note that while you can see the localStorage and sessionStorage items in your debugger, some debuggers don’t automatically update the view. You may have to close your debugger and reopen to see the current state of your resources.

Our other use of sessionStorage is fairly simple as well.

If the username doesn’t exist, we prompt the user for their username and add it both to the namespaced player property and to sessionStorage:

if (!player || player == 'UNKNOWN') {

player = qbdoo.player = prompt('Enter your name') || 'UNKNOWN';

sessionStorage.setItem('user', player);

}

We assign the player name on page load with:

player: sessionStorage.getItem('user') || '',

If the user refreshes the page, the username is captured from sessionStorage. Otherwise, it defaults to blank. While we could have used cookies, this information doesn’t need to be sent back and forth to the server. I could also have used localStorage, but for the case of this book example, I wanted the “security” of when the user closes the browser and the username is deleted, as is what happens with sessionStorage.

The quirk with CubeeDoo is that whoever loses the game gets credit for the full game: if one user starts the game, and pauses it, the cards and current score status are saved in localStorage. Since the username is in sessionStorage, if the browser is closed out after a pause, any username stored in sessionStorage is lost. When a user continues on from the paused state, they can enter a name. If you don’t want a second user to continue on from where the first user left off, even if they are the same person, use sessionStorage instead of localStorage to store the paused state of the game. If you don’t want to bug the user for their username, even if they haven’t played in a month or two, use localStorage instead of sessionStorage to store the username. If you don’t care, still implement one or the other or both. This stuff is fun!

SQL/Database Storage

Web databases were new to HTML5, and were well supported. Actually, Web SQL Database is still well supported, especially in the mobile space. However, the specification was abandoned, and will not be supported in browsers that never supported it (IE and Firefox). Because the alternative, IndexedDB, is not ready for prime time, and Web SQL Database is almost fully supported in WebKit and Opera Mobile browsers, I’m covering it! Realize, however, that Web SQL is obsolete, and is just a stopgap until Android and iOS support IndexDB.

So, what is it? Web databases are databases that are hosted and persisted inside a user’s browser. The client-side SQL database allows for structured data storage: tables with rows and columns, not just name/value pairs. This can be used to store emails locally for an email application or for a cart in an online shopping site. The API to interact with this database is asynchronous, which ensures that the user interface doesn’t lock up (localStorage is synchronous). Because database interaction can occur in multiple browser windows at the same time, the API supports transactions.

Just like SQL, Web SQL has several methods and properties, detailed in the following sections.

Web SQL methods

openDatabase method

An openDatabase() method of the window object takes four parameters: the database name, version, display name, and database size. openDatabase() creates a database object. The database needs to be opened before it can be accessed. You need to define the name, version, description, and the size of the database:

window.openDatabase(database_name, database_version, display_name, db_size);

This method returns a reference to the database, which is referenced for all database transactions.

In CubeeDoo, the high scores can be maintained in two ways: either localStorage or Web SQL. We check to see if Web SQL is supported, and if so use it. If not, we set the script to use localStorage. We fork our code based on the qbdoo.storageType property:

storageType: (!window.openDatabase)? "WEBSQL": 'local',

Because Web SQL is currently supported but will forever be obsolete, don’t use it without first checking for support!

To maintain the high scores in a database, we need to create that database:

var dbSize = 5 * 1024 * 1024; // 5MB variable for dbSize

if (!qbdoo.db) {

if (window.openDatabase) {

qbdoo.db = openDatabase("highscoresDB", "1.0", "Scores", dbsize);

}

}

transaction method

The transaction() method of the database object takes up to three arguments: the transaction, error, and success callback functions. transaction() is a method of the database object we created using the openDatabase() method, not a window object like the openDatabase()method that created the database. You pass it a SQL transaction object on which you can use the executeSQL() method:

db.transaction(transaction_callback, error_callback, success_callback)

executeSQL() method

The executeSQL() method takes one to four arguments: a SQL statement, arguments, a SQL statement callback, and a SQL statement error callback. The SQL statement callback gets passed the transaction object and a SQL statement result object that gives access to the rows:

db.transaction(function(trnactn) {

trnactn.executeSql('SELECT * FROM scores', [], callbackFunc,

db.onError);

});

In CubeeDoo, we combine the two to get and set scores:

saveHighScores: function(score, player) {

qbdoo.db.transaction(function(tx) {

tx.executeSql("INSERT INTO highscoresTable (score, name, date)

VALUES (?, ?, ?)", [score, player, new Date()], onSuccess, qbdoo.onError);

});

function onSuccess(tx, results){

// not needed

}

},

With offline SQL database storage, you can create tables, delete rows, and basically run any SQL command that you might run on your database server. Client-side SQL database storage (Web SQL Database) is supported in Safari, Chrome, and Opera, but will never be supported in Firefox or Internet Explorer. However, since it is supported in WebKit and Opera, it can be used for mobile web applications for improved performance, with localStorage as a fallback until IndexDB is well supported.

CubeeDoo high scores code

As noted earlier, in CubeeDoo we use Web SQL to store the high scores, with a localStorage backup in case Web SQL is not supported. I feature detected, and set the storageType on the qbdoo object:

storageType: (window.openDatabase)? "WEBSQL": 'local',

I then included functions to create the table, save high scores, load the high scores, render the high scores, and to delete the scores from the database. I included a method for sorting high scores for the local storage scores method of retrieving high scores. I didn’t need to sort the Web SQL scores when saving, as I can retrieve sorted scores with SQL using ASC or DESC on the column by which I want to sort.

We have to create the table in our database with the SQL create statement:

createTable: function() {

var i;

qbdoo.db.transaction(function(tx) {

tx.executeSql("CREATE TABLE highscoresTable (id REAL UNIQUE, name TEXT,

score NUMBER, date DATE )", [],

function(tx) {console.log('highscore table created'); },

qbdoo.onError);

});

},

We save high scores with the SQL insert statement, or we store in localStorage with the setItem() method if the browser doesn’t support Web SQL:

saveHighScores: function(score, player) {

if (qbdoo.storageType === 'local') {

localStorage.setItem("highScores", JSON.stringify(qbdoo.highScores));

} else {

qbdoo.db.transaction(function(tx) {

tx.executeSql("INSERT INTO highscoresTable (score, name, date)

VALUES (?, ?, ?)", [score, player, new Date()],

onSuccess,

qbdoo.onError);

});

function onSuccess(tx,results){

// not needed

}

}

},

We have two functions to load high scores depending on whether we’re using localStorage or Web SQL. We use the SQL select statement to select the high scores from the database, sorting in descending order:

loadHighScoresLocal: function() {

var scores = localStorage.getItem("highScores");

if (scores) {

qbdoo.highScores = JSON.parse(scores);

}

if (qbdoo.storageType === 'local') {

qbdoo.sortHighScores();

}

},

loadHighScoresSQL: function(){

var i, item;

qbdoo.db.transaction(function(tx) {

tx.executeSql("SELECT score, name, date FROM highscoresTable

ORDER BY score DESC", [], function(tx, result) {

for (i = 0, item = null; i < result.rows.length; i++) {

item = result.rows.item(i);

qbdoo.highScores[i] = [item['score'], item['name'], item['date']];

} //end for

}, onError); // end execute

function onError(tx, error) {

if (error.message.indexOf('no such table')) {

qbdoo.createTable();

} else {

console.log('Error: ' + error.message);

}

}

qbdoo.renderHighScores();

}); // end transaction

},

The renderHighScores() function creates a list of the high scores:

// put the high scores on the screen

renderHighScores: function(score, player) {

var classname, highlighted = false, text = '', i;

for (i = 0; i < qbdoo.maxHighScores; i++) {

if (i < qbdoo.highScores.length) {

if (qbdoo.highScores[i][1] == player && qbdoo.highScores[i][0] == score) {

classname = ' class="current"';

} else {

classname = '';

}

text += "<li" + classname + ">" + qbdoo.highScores[i][1].toUpperCase() +

": <em>" + parseInt(qbdoo.highScores[i][0]) + "</em></li> ";

}

}

qbdoo.highscorelist.innerHTML = text;

},

The SQL drop statement can be used to delete the table if the user chooses to delete the scores. If Web SQL is not supported, the reset() function uses the localStorage removeItem() method:

eraseScores: function() {

if (qbdoo.storageType === 'local') {

qbdoo.reset("highScores");

} else {

qbdoo.db.transaction(function(tx) {

tx.executeSql("DROP TABLE highscoresTable", [],

qbdoo.createTable,

qbdoo.onError);

});

}

qbdoo.highscorelist.innerHTML = '<li></li>';

},

onError: function(tx, error) {

console.log('Error: ' + error.message);

},

reset: function(item) {

localStorage.removeItem(item);

}

IndexedDB

For client-side storage of structured data, we will soon have IndexedDB. IndexedDB, when finalized and supported, will provide for high-performance data searches using indexes. While DOM Storage is useful for storing smaller amounts of data, IndexedDB provides for an asynchronous solution for storing larger amounts of structured data. Since it is not currently widely supported in mobile browsers, we are not covering it here. If you prefer to use APIs that are actually moving forward as specifications, there is a polyfill to enable using IndexedDB syntax in Web SQL supporting browsers.

As support improves, I will add IndexedDB to the online chapter resources. At the time of this writing, the only support in the mobile space is in IE10, and prefixed with different syntax in BlackBerry 10.

Enhanced User Experience

In addition to providing for offline web applications and uniform support for media, HTML5 includes several APIs that enable developers to enhance user experience. HTML5 includes a geolocation API enabling browsers to determine user location (with user consent, of course), web workers to improve script runtime of web applications, microdata to improve the semantics of web content, cross-document messaging API should allow documents to safely communicate with each other regardless of their source domain, and ARIA, to enable developers making the rich Internet applications accessible.

Geolocation

Geolocation allows users to share their physical location with your application if they choose to. Especially useful in social networking, geotagging, and mapping (but applicable to any type of application), geolocation enables developers to enhance the user experience, making content, social graphs, and advertisements more relevant to the location of the user.

The browser will request the permission of the user before accessing geolocation information (see Figure 6-2). Geolocation is an opt-in feature: when your web application requests geolocation information, the browser will ask the user if permission to share geolocation information is granted via banner or alert. The user can grant permission or deny it, and optionally remember the choice on that site. If permission is granted, the geolocation information will be accessible to your scripts and any third-party scripts included in the page, letting your application determine the location of the user, and capable of updating location information as the user moves around.

Permission must be granted for a website to receive a user’s geolocation information

Figure 6-2. Permission must be granted for a website to receive a user’s geolocation information

Location information is approximate, garnered from IP addresses, cell towers, WiFi networks, GPS, or even getting the information through manual data entry by the user. While approximate, you’ll notice that it can be freakishly accurate.

The geolocation API does not care how the client determines location as long as the data is received in a standard way. The geolocation API is asynchronous.

To determine browser support for geolocation, use:

if (navigator.geolocation) {

//geolocation is supported

}

The geolocation object provides for the getCurrentPosition() and watchCurrentPosition() methods that asynchronously return the user’s current location, either once or continuously. The watchCurrentPosition() method can be used for active location applications such as GPS/navigation applications. For our web application, we don’t need direction information, so the getCurrentPosition() method would suit our needs, and wouldn’t drain the battery by repeatedly seeking location information. We don’t need location information for CubeeDoo, but we can still learn it:

if (navigator.geolocation) {

navigator.geolocation.getCurrentPosition(handle_success, handle_errors);

}

If successful, the callback function returns the current position with the coords object containing the more commonly used latitude and longitude properties, as well as the altitude, accuracy, altitudeAccuracy, heading, and speed properties. The following script will return the alert with the current latitude and longitude, and is available in the online chapter resources:

if (navigator.geolocation) {

navigator.geolocation.getCurrentPosition(handle_success,handle_errors);

function handle_success(position) {

alert('Latitude: ' + position.coords.latitude + '\n Longitude: '

+ position.coords.latitude);

}

function handle_errors(err) {

switch(err.code) {

case err.PERMISSION_DENIED:

alert("User refused to share geolocation data");

break;

case err.POSITION_UNAVAILABLE:

alert("Current position is unavailable");

break;

case err.TIMEOUT:

alert("Timed out");

break;

default:

alert("Unknown error");

break;

}

}

}

If successful, both the getCurrentPosition() and watchCurrentPosition() methods success callbacks will return a location object with the coords object, with the following properties:

§ position.coords.latitude

§ position.coords.longitude

§ position.coords.altitude

§ position.coords.accuracy

The watchCurrentPosition() method also returns the following properties:

§ position.coords.heading

§ position.coords.speed

The properties are kind of self-explanatory. Other than the Kindle and Opera Mini, geolocation is supported everywhere, and has been for a while (since IE9 on desktop).

We don’t need location information for CubeeDoo, but we did include pinpointing current location on a map as an example in the files:

1 function getLocation() {

2 if (navigator.geolocation) {

3 navigator.geolocation.getCurrentPosition(success, error);

4 console.log('got position');

5 } else {

6 error('not supported');

7 }

8 }

9 function error(text) {

10 text = text || 'failed';

11 console.log(text);

12 }

13 function success(location) {

14 var lat = location.coords.latitude;

15 var long = location.coords.longitude;

16 var url = "http://maps.google.com/maps?q=" + lat + "," + long;

17 }

The getLocation() function feature detects in line 2 to see if geolocation is supported by the browser. Note that geolocation is on the navigator object (rather than window or document, like most method and properties we use). We get the current position with thegetCurrentPosition() method of the geolocation object in line 3. If successful, the success callback uses the returned coords object’s latitude and longitude properties to add a pinpoint to a Google map in lines 14–16. We included an error callback function on line 9. The message is usually a timeout, permission denied, or position unavailable if there is a failure.

Web Workers

All of the JavaScript, including page reflows and repaints, runs on the single UI thread that also handles repainting of user interactions, non-hardware-accelerated animations, etc. If there is a task with a heavy script load, the browser may slow to a crawl, greatly harming user experience. Web workers allow JavaScript to delegate heavy tasks to other processes so that scripts run in parallel. This enables the main thread to do the exciting UI stuff while the web worker does any heavy lifting you pass to the worker without slowing the main script thread. Web workers are useful in allowing your code to perform processor-intensive calculations without blocking the user interface thread.

Workers enable web content to run scripts in background threads, even AJAX. The worker thread can perform tasks without interfering with the user interface.

You know how sometimes web applications take a long time and you have to wait for the hourglass or rainbow beach ball to disappear before being able to interact with the page? Web workers are a solution. Workers perform JavaScript on a background thread leaving the main UI thread free to manipulate the DOM and repaint the page, if necessary. Web workers cannot manipulate the DOM. If actions taken by the background thread need to result in changes to the DOM, they should post messages back to their creators to do that work. The postMessage() method can be employed.

If your JavaScript includes some resource-intensive calculations, you can pass this to a web worker to process while the main thread continues running. Before creating a worker, ensure that the browser supports web workers. Web workers support heavy JavaScript processing that might crash a non-web-worker–supporting browser:

if (window.Worker) {

//browser supports web workers

}

To create a web worker, you call the Worker() constructor, specifying the URI of a script to execute in the worker thread. The URI is relative to the file calling the script:

if (window.Worker) {

var webWorker = new Worker('subcontractor.js'); //create it

}

Communicating with the web worker is accomplished via the postMessage() method. Once created, set the worker’s onmessage property to an appropriate event handler function to receive notifications from the web worker. You terminate a worker with the terminate() or close()method. The terminate() method is immediate, stopping all current processes:

if (window.Worker) {

var webWorker = new Worker('subcontractor.js'); //create it

webWorker.postMessage(some_message);

}

In the worker (in this case, the subcontractor.js), receive the message from the main thread and act on it:

//in the subcontractor.js file

self.onmessage = function(event) {

// handle the message

var stuff = event.data;

// and send it back to the main thread

postMessage(stuff);

};

Workers can use timeouts and intervals just like the main thread can. This can be useful, for example, if you want to have your worker thread run code periodically instead of nonstop. You can control the worker by employing the setTimeout(), clearTimeout(), setInterval(), andclearInterval() methods.

Worker threads have access to a global function, importScripts(), which lets them import scripts or libraries into their scope. It accepts as parameters zero or more URIs of resources to import:

/* imports two scripts */

importScripts('scripts/jquery-min.js', 'application.js');

Web workers do not have access to the DOM. They don’t have access to the console either. You may also get a security error when testing locally, which can make developing with web workers a little more difficult than regular JavaScript.

In CubeeDoo, we have no intensive JavaScript that we need to run, or background AJAX processes. While we don’t employ the benefits of web workers in CubeeDoo, there is a Fibonacci sequence example in the online chapter resources. Also, we’ve included a web worker to handle our high score sort function. Sorting five numbers is certainly doable without the need of a web worker. However, if we kept the top 1,000,000 scores, sorting that many values would be a good use of a web worker (but a bad use of localStorage).

Were we to have used web workers for sorting the high scores, we would have replaced our sorting function with a web worker call:

var webWorker = new Worker('js/sort.js');

webWorker.postMessage(qbdoo.highscores);

webWorker.onmessage(function(event) {

qbdoo.highscores(event.data);

});

The web worker script, in turn, needs to expect and accept the highscores via the onmessage property, and needs to post back the sorted scores via postMessage:

self.onmessage = function(event) {

var sortedScores = sortScores(event.data);

self.postMessage(sortedScores);

};

Microdata

Another feature of HTML5 is microdata. While microdata will not impact your site in any visible way, it is an increasingly relevant feature when it comes to search engine optimization and data scraping.

Microdata will replace the need for microformats. Microformats are standardized sets of vocabularies that are both human and machine-readable. They are web page conventions used to describe common information types including events, reviews, address book information, and calendar events via class attributes. Each entity, such as a person, event, or business, has its own properties, such as name, address, and phone number.

Microdata lets you create your own vocabularies beyond HTML5 and extend your web pages with custom semantics. Microdata uses the new to HTML5 attributes of itemscope, itemprop, itemref, and itemtype.

The itemscope attribute is used to create an item, indicating that the scope of the item begins in the opening tag in which the attribute is included, and ends at that element’s closing tag. The itemprop, or item property attribute, is used to add a property to an item. If an itemprop has associated properties that are not descendants of that itemprop, you can associate those properties with that itemprop by using the itemref, or item reference attribute. The entity that has the itemscope attribute also accepts the itemref attribute that takes as its value a space-separated list of IDs of entities that should be crawled in addition to the itemprop’s descendants.

Microdata is most useful when it is used in contexts where other authors and readers are able to cooperate to make new uses of the markup. You can create your own types of microdata or use predefined data vocabularies. Some predefined vocabularies can be found at http://www.data-vocabulary.org/.

Microdata versus microformats

Microformats are very similar to microdata. In fact, microdata can be viewed as an extension of the existing microformat idea, which attempts to address the deficiencies of microformats without the complexity of the often preferred systems like RDFa. Instead of using the new itemscope,itemprop, itemtype, etc., attributes, Microformats repurpose the class attribute to provide human and machine-readable semantic meaning to data—in essence, microdata.

In general, microformats use the class attribute in the opening HTML tags (often <span> or <div>) to assign brief, descriptive names to entities and their properties. Unlike microdata, microformats are not part of the HTML5 specification.

Here’s an example of a short HTML block showing my contact information for myself:

<ul>

<li><img src="http://standardista.com/images/estelle.jpg"

alt="photo of Estelle Weyl"/></li>

<li><a href="http://www.standardista.com">Estelle Weyl</a></li>

<li>1234 Main Street<br />San Francisco, CA 94114</li>

<li>415.555.1212</li>

</ul>

Here is the same HTML marked up with the hCard (person) microformat.

<ul id="hcard-Estelle-Weyl" class="vcard">

<li><img src="http://standardista.com/images/estelle.jpg"

alt="photo of Estelle Weyl" class="photo"/></li>

<li><a class="url fn" href="http://www.standardista.com">Estelle Weyl</a></li>

<li class="adr">

<span class="street-address">1234 Main Street</span>

<span class="locality">San Francisco</span>, <span class="region">CA</span>,

<span class="postal-code">94114</span>

<span class="country-name hidden">USA</span>

</li>

<li class="tel">415.555.1212</li>

</ul>

In the first line, class="vcard" indicates that the HTML enclosed in the <ul> describes a person: in this case, me. The microformat used to describe people is called hCard but is referred to in HTML as vCard. While confusing, it isn’t a typo.

The rest of the example describes properties of the person, including a photo, name, address, URL, and phone, with each property having a class attribute describing the property. For example, fn describes my “full name.”

Properties can contain other properties. In the example, the property adr encompasses all the components of my fake address, including street address, locality, region, and postal code. With a little CSS, we can hide elements with class hidden and add a line break between street address and locality. To create your own hCard, visit http://microformats.org/code/hcard/creator.

The same content could be written with microdata:

<ul id="hcard-Estelle-Weyl" itemscope

itemtype="http://microformats.org/profile/hcard">

<li><img src="http://standardista.com/images/estelle.jpg"

alt="photo of Estelle Weyl" class="photo"/></li>

<li><a href="http://www.standardista.com" itemprop="fn">Estelle Weyl</a></li>

<li itemprop="adr">

<span itemprop="street-address">1234 Main Street</span>

<span itemprop="locality">San Francisco</span>,

<span itemprop="region">CA</span>,

<span itemprop="postal-code">94114</span>

<span class="hidden" itemprop="country-name">USA</span>

</li>

<li itemprop="tel">415.555.1212</li>

</ul>

Or you can combine the two:

<ul id="hcard-Estelle-Weyl" class="vcard" itemscope

itemtype="http://microformats.org/profile/hcard">

<li><img src="http://standardista.com/images/estelle.jpg"

alt="photo of Estelle Weyl" class="photo"/></li>

<li><a class="url fn" href="http://www.standardista.com"

itemprop="fn">Estelle Weyl</a></li>

<li class="adr" itemprop="adr">

<span class="street-address" itemprop="street-address">1234 Main Street</span>

<span class="locality" itemprop="locality">San Francisco</span>,

<span class="region" itemprop="region">CA</span>,

<span class="postal-code" itemprop="postal-code">94114</span>

<span class="country-name hidden" itemprop="country-name">USA</span>

</li>

<li class="tel" itemprop="tel">415.555.1212</li>

</ul>

Microdata does not alter the appearance of a document. Rather, it just enhances the semantics of that document. Search engines will not display content that is not visible to the user. Providing search engines with more detailed information, even if you don’t want that information to be seen by visitors to your page, can be helpful. To enable the Web to be a single global database, being able to parse the available data into meaningful data points is required. Microdata and microformats help make otherwise nondescript data meaningful to parsers.

Microdata API

Not yet well supported, the microdata DOM API provides access to the microdata items. The document.getItems(itemType) returns a nodeList containing the items with the specified types, or all types if no argument is specified. The document.getItems() method returns a nodeList containing all the microdata items on a page when no argument is passed. You can specify a specific itemtype URL as the argument to return only items of that type.

Once you’ve returned your nodeList, you can then access the different properties with the properties attribute:

var allMicrodata = document.getItems();

var firstItemName = allMicrodata.properties['name'][0].itemValue;

Each item is represented in the DOM by the element on which the relevant itemscope attribute is found.

Cross-Document Messaging

Cross-document messaging allows documents to communicate with each other regardless of their source domain, in a way designed to protect us from cross-site scripting attacks.

Web applications often include services from several different domains. The current way of doing mash-ups has many security risks. When you include third-party JavaScript on your website, those external scripts, over which you have no control, have access to your domain’s cookies and can forge requests that appear to come from the user. Iframes are not the solution to the problem, since your document cannot communicate with the contents of an iframe embedded within the page if that iframe comes from a different domain.

The HTML5 cross-document messaging API attempts to solve both of these issues by enabling the registration of event handlers for incoming messages from other domains, and sending messages to other domains.

To verify that the message is coming from the expected domain:

window.addEventListener('message', function(e) {

if (e.origin == 'http://the_domain.com') {

// the origin of the message is verified. Test to see if

// it's in the correct format before using

}, false);

Send a message to another domain:

var theFrame = document.getElementById("myIFrame").contentWindow;

theFrame.postMessage("The message", "http://www.the_domain.com");

CORS: Cross-Origin Resource Sharing

As CubeeDoo is a small, self-contained application with limited visitors and no third-party app integration, we aren’t using any cross-document messaging or cross-origin resource sharing (CORS). If you are creating more popular applications, you’ll likely be using content delivery networks (CDN) or integrating third-party applications. For example, if we were hosting our font on a CDN, we would need to use CORS to tell Firefox and Internet Explorer that it is OK to render fonts from a different domain.

Security

Security is a major concern in cross-domain messaging. Always check the origin property to ensure that messages are only accepted from domains that you expect to receive messages from. After you’ve confirmed that the message is coming from the expected server, confirm that the data received is in the expected format. You should never rely on someone else’s server not being compromised.

Accessible Rich Internet Applications (ARIA)

Accessibility

HTML5, just like prior versions, can be made to be completely accessible. It just requires a little bit of planning. ARIA, or Accessible Rich Internet Applications, is the first part of HTML5 that is supported by all modern browsers. Most popular JavaScript libraries also provide support for ARIA implementation. In addition to ARIA, HTML5 provides the ability to enhance accessibility in the fact that it does not use Flash (yes, they talk the accessibility talk, but no one has figured out how to make Flash walk the accessibility walk) or other embedded objects. It uses <video>,<audio>, <svg>, and <canvas>, HTML elements that, with the exception of <canvas>, are inherently accessible. As part of the DOM, they are easily targetable to increase accessibility.

As we create more and more dynamic web applications, the content becomes less and less inherently accessible to differently abled users. You can use ARIA properties to provide the basic type, state, and changes created via JavaScript widgets to screen readers and other assistive technologies. For example, when checking for airfares, selecting a checkbox entitled “nonstop only” may lead to a dynamic response that updates the airfare rates without reloading the page. If a user is visually impaired, how do they know that part of the page was updated since they can’t actually see the update? On a finance page, how does a user know that the stock ticker is continually updated? The ARIA API provides for unobtrusive (and obtrusive) ways of providing such information to the user.

As previously stated, ARIA stands for Accessible Rich Internet Applications. With the proliferation of Internet applications, there has been an increase in the number of sites requiring JavaScript and that update without page refreshes. This imposes accessibility issues that weren’t addressed by Web Content Accessibility Guidelines, or WCAG 1, as those specifications were written when “sites must work without JavaScript” was a reasonable accessibility specification.

With the increase of web-based “applications” (versus “sites”) requiring JavaScript, and improved support of JavaScript in assistive technologies, new accessibility issues have emerged. ARIA attempts to handle some of those issues. Through the inclusion of roles, states, and properties, your dynamically generated content can be made accessible to assistive technologies. Additionally, static content can be made more accessible through these additional, enhanced semantic cues.

By including ARIA accessibility features on your website, you are enhancing the accessibility of your site or application. By including roles, states, and properties, ARIA enables the developer to make the code semantically richer for the assistive technology user. ARIA enables semantic description of element or widget behavior and enables information about groups and the elements within them. ARIA states and properties are accessible via the DOM.

Similar to including the title attribute, ARIA is purely an enhancement and will not harm your site in any way. In other words, there is no valid reason to not include these features! Most JavaScript libraries, such as jQuery and Dojo, already support ARIA. Modern browsers, including IE8, support ARIA.

The easiest to include and most important properties of ARIA are the inclusions for the role attribute, and inclusion of states and properties:

§ Only use ARIA roles, attributes, and properties when regular HTML markup does not support all of the semantics required.

§ Apply the ARIA role attribute in cases where the markup needs to be semantically enhanced and in cases where elements are being employed outside of their semantic intent. This includes setting up relationships between related elements (grouping).

§ Set the properties and initial state on dynamic and user-changing elements. States, such as “checked,” are properties that may change often. Assistive technology that supports ARIA will react to state and property changes.

§ Support full, usable keyboard navigation. Elements should all be able to have keyboard focus.

§ Make the user interface visually match the defined states and properties in browsers that support the ARIA CSS pseudoclasses.

The role attribute enables the developer to create semantic structure on repurposed elements. While to a sighted user, the example of a span repurposed as a checkbox is not noticeable, the role attribute makes this seemingly nonsemantic markup accessible, usable, and interoperable with assistive technologies. Once set, however, a role attribute’s value should not be dynamically changed, since this will confuse the assistive technology.

Example: your designer insists that they want the checkboxes on your page to look a certain way. “Impossible,” you say. You know that you can use CSS to make a span look like a checkbox. The sighted user would never know that you weren’t using <input type="checkbox"..., but for accessibility concerns, you know a screen reader user will not know it’s a checkbox. With the ARIA role attribute included in your code, and both a browser and screen reader that support ARIA, you can make your repurposed span accessible with:

<span role="checkbox" aria-checked="true" tabindex="0"/>

It’s not enough to simply use role in the preceding example. If you include spans transformed into checkboxes, you will need to include equivalent but unobtrusive touch, keyboard, and mouse events for each interaction. Best practices dictate that you should use the most semantically appropriate element for the job, so in practice you should not do this.

There are currently over 60 roles, including:

alert

dialog

listitem

option

spinbutton

alertdialog

directory

log

presentation

status

application

document

main

progressbar

tab

article

form

marquee

radio

tablist

banner

grid

math

radiogroup

tabpanel

button

gridcell

menu

region

textbox

checkbox

group

menubar

row

timer

columnheader

heading

menuitem

rowgroup

toolbar

combobox

img

menuitemcheckbox

search

tooltip

complementary

link

menuitemradio

scrollbar

tree

contentinfo

list

navigation

separator

treegrid

definition

listbox

note

slider

treeitem

There is no need to include role if an element is employed as intended (you don’t have to include role="checkbox" on <input type="checkbox"/>). However, if you use a span to appear and function like a checkbox, include the ARIA role attribute: <span role="checkbox">. Choose the role type from this list that is most similar to the role you are assigning to the element you are employing in a nonsemantically correct manner.

If you are interested in learning more about WAI-ARIA, Web Accessibility Initiative—Accessible Rich Internet Applications, http://www.w3.org/WAI/intro/aria.php is the place to start.

In Conclusion

This chapter is intended to give you an idea of the APIs that are being included in HTML5. Each of these sections merit books of their own. In fact, some, like microformats, already have their own books. In other cases, such as cross-document messaging, the issue is too nascent to write about. Please check out the online chapter resources for links to more in-depth articles on each of these topics.


[40] For more information on sessionStorage, see http://www.nczonline.net/blog/2009/07/21/introduction-to-sessionstorage/.