Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
342 lines (242 sloc) 31.5 KB
<!DOCTYPE html>
<meta charset=utf-8>
<title>Local Storage - Dive Into HTML5</title>
<!--[if lt IE 9]><script src=j/html5.js></script><![endif]-->
<link rel=alternate type=application/atom+xml href=>
<link rel=stylesheet href=screen.css>
body{counter-reset:h1 7}
<link rel=stylesheet media='only screen and (max-device-width: 480px)' href=mobile.css>
<link rel=prefetch href=detect.html>
<p>You are here: <a href=index.html>Home</a> <span class=u>&#8227;</span> <a href=table-of-contents.html#storage>Dive Into <abbr>HTML5</abbr></a> <span class=u>&#8227;</span>
<h1><br>The Past, Present &amp; Future of Local Storage for Web Applications</h1>
<p id=toc>&nbsp;
<p class=a>&#x2767;
<h2 id=divingin>Diving In</h2>
<p class=f><img src=i/aoc-p.png alt=P width=110 height=106>ersistent local storage is one of the areas where native client applications have held an advantage over web applications. For native applications, the operating system typically provides an abstraction layer for storing and retrieving application-specific data like preferences or runtime state. These values may be stored in the registry, INI files, XML files, or some other place according to platform convention. If your native client application needs local storage beyond key/value pairs, you can embed your own database, invent your own file format, or any number of other solutions.
<p>Historically, web applications have had none of these luxuries. Cookies were invented early in the web&#8217;s history, and indeed they can be used for persistent local storage of small amounts of data. But they have three potentially dealbreaking downsides:
<li>Cookies are included with every <abbr>HTTP</abbr> request, thereby slowing down your web application by needlessly transmitting the same data over and over
<li>Cookies are included with every <abbr>HTTP</abbr> request, thereby sending data unencrypted over the internet (unless your entire web application is served over <abbr>SSL</abbr>)
<li>Cookies are limited to about 4 KB of data &mdash; enough to slow down your application (see above), but not enough to be terribly useful
<p>What we really want is
<li>a lot of storage space
<li>on the client
<li>that persists beyond a page refresh
<li>and isn&#8217;t transmitted to the server
<p>Before <abbr>HTML5</abbr>, all attempts to achieve this were ultimately unsatisfactory in different ways.
<p class=a>&#x2767;
<h2 id=history>A Brief History of Local Storage Hacks Before HTML5</h2>
<p>In the beginning, there was only Internet Explorer. Or at least, that&#8217;s what Microsoft wanted the world to think. To that end, as part of the <a href="">First Great Browser Wars</a>, Microsoft invented a great many things and included them in their browser-to-end-all-browser-wars, Internet Explorer. One of these things was called <a href=""><abbr>DHTML</abbr> Behaviors</a>, and one of these behaviors was called <a href="">userData</a>.
<p><code>userData</code> allows web pages to store up to 64 KB of data per domain, in a hierarchical XML-based structure. (Trusted domains, such as intranet sites, can store 10 times that amount. And hey, <a href="">640 KB ought to be enough for anybody</a>.) IE does not present any form of permissions dialog, and there is no allowance for increasing the amount of storage available.
<p>In 2002, Adobe introduced a feature in Flash 6 that gained the unfortunate and misleading name of &#8220;Flash cookies.&#8221; Within the Flash environment, the feature is properly known as <a href="">Local Shared Objects</a>. Briefly, it allows Flash objects to store up to 100 KB of data per domain. Brad Neuberg developed an early prototype of a Flash-to-JavaScript bridge called <a href="">AMASS</a> (AJAX Massive Storage System), but it was limited by some of Flash&#8217;s design quirks. By 2006, with the advent of <a href="">ExternalInterface</a> in Flash 8, accessing <abbr>LSO</abbr>s from JavaScript became an order of magnitude easier and faster. Brad rewrote <abbr>AMASS</abbr> and integrated it into the popular <a href=>Dojo Toolkit</a> under the moniker <a href=""><code></code></a>. Flash gives each domain 100 KB of storage &#8220;for free.&#8221; Beyond that, it prompts the user for each order of magnitude increase in data storage (1 Mb, 10 Mb, and so on).
<p>In 2007, Google launched <a href="">Gears</a>, an open source browser plugin aimed at providing additional capabilities in browsers. (We&#8217;ve previously discussed Gears in the context of <a href=geolocation.html#ie>providing a geolocation <abbr>API</abbr> in Internet Explorer</a>.) Gears provides <a href="">an <abbr>API</abbr> to an embedded <abbr>SQL</abbr> database</a> based on <a href="">SQLite</a>. After obtaining permission from the user once, Gears can store unlimited amounts of data per domain in <abbr>SQL</abbr> database tables.
<p>In the meantime, Brad Neuberg and others continued to hack away on <code></code> to provide a unified interface to all these different plugins and <abbr>API</abbr>s. By 2009, <code></code> could auto-detect (and provide a unified interface on top of) Adobe Flash, Gears, Adobe AIR, and an early prototype of <abbr>HTML5</abbr> storage that was only implemented in older versions of Firefox.
<p>As you survey these solutions, a pattern emerges: all of them are either specific to a single browser, or reliant on a third-party plugin. Despite heroic efforts to paper over the differences (in <code></code>), they all expose radically different interfaces, have different storage limitations, and present different user experiences. So this is the problem that <abbr>HTML5</abbr> set out to solve: to provide a standardized <abbr>API</abbr>, implemented natively and consistently in multiple browsers, without having to rely on third-party plugins.
<p class=a>&#x2767;
<h2 id=localstorage>Introducing HTML5 Storage</h2>
<p>What I will refer to as &#8220;<abbr>HTML5</abbr> Storage&#8221; is a specification named <a href="">Web Storage</a>, which was at one time part of the <abbr>HTML5</abbr> specification proper, but was split out into its own specification for uninteresting political reasons. Certain browser vendors also refer to it as &#8220;Local Storage&#8221; or &#8220;<abbr>DOM</abbr> Storage.&#8221; The naming situation is made even more complicated by some related, similarly-named, emerging standards that I&#8217;ll discuss later in this chapter.
<p>So what is <abbr>HTML5</abbr> Storage? Simply put, it&#8217;s a way for web pages to store named key/value pairs locally, within the client web browser. Like cookies, this data persists even after you navigate away from the web site, close your browser tab, exit your browser, or what have you. Unlike cookies, this data is never transmitted to the remote web server (unless you go out of your way to send it manually). Unlike <a href=#history>all previous attempts</a> at providing persistent local storage, it is implemented natively in web browsers, so it is available even when third-party browser plugins are not.
<p>Which browsers? Well, the latest version of pretty much every browser supports <abbr>HTML5</abbr> Storage&hellip; even Internet Explorer!
<table class=bc>
<caption><abbr>HTML5</abbr> Storage support</caption>
<tr><th title="Internet Explorer">IE<th title="Mozilla Firefox">Firefox<th title="Apple Safari">Safari<th title="Google Chrome">Chrome<th>Opera<th>iPhone<th>Android
<p>From your JavaScript code, you&#8217;ll access <abbr>HTML5</abbr> Storage through the <code>localStorage</code> object on the global <code>window</code> object. Before you can use it, you should <a href=detect.html#storage>detect whether the browser supports it</a>.
<p class="legend top" style="padding-left:6em"><span class=arrow>&#x21b6;</span> check for <abbr>HTML5</abbr> Storage
<pre><code>function supports_html5_storage() {
try {
return 'localStorage' in window && window['localStorage'] !== null;
} catch (e) {
return false;
<p>Instead of writing this function yourself, you can use <a href=detect.html#modernizr>Modernizr</a> to detect support for <abbr>HTML5</abbr> Storage.
<pre><code>if (<mark>Modernizr.localstorage</mark>) {
// window.localStorage is available!
} else {
// no native support for HTML5 storage :(
// maybe try or a third-party solution
<p class=a>&#x2767;
<h2 id=methods>Using HTML5 Storage</h2>
<p><abbr>HTML5</abbr> Storage is based on named key/value pairs. You store data based on a named key, then you can retrieve that data with the same key. The named key is a string. The data can be any type supported by JavaScript, including strings, Booleans, integers, or floats. However, the data is actually stored as a string. If you are storing and retrieving anything other than strings, you will need to use functions like <code>parseInt()</code> or <code>parseFloat()</code> to coerce your retrieved data into the expected JavaScript datatype.
<pre><code>interface Storage {
getter any getItem(in DOMString key);
setter creator void setItem(in DOMString key, in any data);
<p>Calling <code>setItem()</code> with a named key that already exists will silently overwrite the previous value. Calling <code>getItem()</code> with a non-existent key will return <code>null</code> rather than throw an exception.
<p>Like other JavaScript objects, you can treat the <code>localStorage</code> object as an associative array. Instead of using the <code>getItem()</code> and <code>setItem()</code> methods, you can simply use square brackets. For example, this snippet of code:
<pre><code>var foo = localStorage.<mark>getItem</mark>("bar");
// ...
localStorage.<mark>setItem</mark>("bar", foo);</code></pre>
<p>&hellip;could be rewritten to use square bracket syntax instead:
<pre><code>var foo = localStorage<mark>["bar"]</mark>;
// ...
localStorage<mark>["bar"]</mark> = foo;</code></pre>
<p>There are also methods for removing the value for a given named key, and clearing the entire storage area (that is, deleting all the keys and values at once).
<pre><code>interface Storage {
deleter void removeItem(in DOMString key);
void clear();
<p>Calling <code>removeItem()</code> with a non-existent key will do nothing.
<p>Finally, there is a property to get the total number of values in the storage area, and to iterate through all of the keys by index (to get the name of each key).
<pre><code>interface Storage {
readonly attribute unsigned long length;
getter DOMString key(in unsigned long index);
<p>If you call <code>key()</code> with an index that is not between 0&ndash;(<code>length</code>-1), the function will return <code>null</code>.
<h3 id=storage-event>Tracking Changes to the HTML5 Storage Area</h3>
<p>If you want to keep track programmatically of when the storage area changes, you can trap the <code>storage</code> event. The <code>storage</code> event is fired on the <code>window</code> object whenever <code>setItem()</code>, <code>removeItem()</code>, or <code>clear()</code> is called <em>and actually changes something</em>. For example, if you set an item to its existing value or call <code>clear()</code> when there are no named keys, the <code>storage</code> event will not fire, because nothing actually changed in the storage area.
<p>The <code>storage</code> event is supported everywhere the <code>localStorage</code> object is supported, which includes Internet Explorer 8. IE 8 does not support the W3C standard <code>addEventListener</code> (although that will finally be added in IE 9). Therefore, to hook the <code>storage</code> event, you&#8217;ll need to check which event mechanism the browser supports. (If you&#8217;ve done this before with other events, you can skip to the end of this section. Trapping the <code>storage</code> event works the same as every other event you&#8217;ve ever trapped. If you prefer to use jQuery or some other JavaScript library to register your event handlers, you can do that with the <code>storage</code> event, too.)
<pre><code>if (window.addEventListener) {
window.addEventListener("storage", handle_storage, false);
} else {
window.attachEvent("onstorage", handle_storage);
<p>The <code>handle_storage</code> callback function will be called with a <code>StorageEvent</code> object, except in Internet Explorer where the event object is stored in <code>window.event</code>.
<pre><code>function handle_storage(e) {
if (!e) { e = window.event; }
<p>At this point, the variable <code>e</code> will be a <code>StorageEvent</code> object, which has the following useful properties.
<table class=st>
<caption>StorageEvent Object</caption>
<tr class=ho><th>Property<th>Type<th>Description
<tr class=zebra><th><code>key</code><td>string<td>the named key that was added, removed, or modified
<tr><th><code>oldValue</code><td>any<td>the previous value (now overwritten), or <code>null</code> if a new item was added
<tr class=zebra><th><code>newValue</code><td>any<td>the new value, or <code>null</code> if an item was removed
<tr><th><code>url</code><sup>*</sup><td>string<td>the page which called a method that triggered this change
<tr><td colspan=3>* Note: the <code>url</code> property was originally called <code>uri</code>. Some browsers shipped with that property before the specification changed. For maximum compatibility, you should check whether the <code>url</code> property exists, and if not, check for the <code>uri</code> property instead.
<p>The <code>storage</code> event is not cancelable. From within the <code>handle_storage</code> callback function, there is no way to stop the change from occurring. It&#8217;s simply a way for the browser to tell you, &#8220;hey, this just happened. There&#8217;s nothing you can do about it now; I just wanted to let you know.&#8221;
<h3 id=limitations>Limitations in Current Browsers</h3>
<p>In talking about <a href=#history>the history of local storage hacks</a> using third-party plugins, I made a point of mentioning the limitations of each technique, such as storage limits. I just realized that I haven&#8217;t mentioned anything about the limitations of the now-standardized <abbr>HTML5</abbr> Storage. I&#8217;ll give you the answers first, then explain them. The answers, in order of importance, are &#8220;5 megabytes,&#8221; &#8220;<code>QUOTA_EXCEEDED_ERR</code>,&#8221; and &#8220;no.&#8221;
<p>&#8220;5 megabytes&#8221; is how much storage space each <a href=>origin</a> gets by default. This is surprisingly consistent across browsers, although it is phrased as no more than a suggestion in the <abbr>HTML5</abbr> Storage specification. One thing to keep in mind is that you&#8217;re storing strings, not data in its original format. If you&#8217;re storing a lot of integers or floats, the difference in representation can really add up. Each digit in that float is being stored as a character, not in the usual representation of a floating point number.
<p>&#8220;<code>QUOTA_EXCEEDED_ERR</code>&#8221; is the exception that will get thrown if you exceed your storage quota of 5 megabytes. &#8220;No&#8221; is the answer to the next obvious question, &#8220;Can I ask the user for more storage space?&#8221; At time of writing (February 2011), no browser supports any mechanism for web developers to request more storage space. Some browsers (<a href=>like Opera</a>) allow the user to control each site&#8217;s storage quota, but it is purely a user-initiated action, not something that you as a web developer can build into your web application.
<p class=a>&#x2767;
<h2 id=halma>HTML5 Storage in Action</h2>
<p>Let&#8217;s see <abbr>HTML5</abbr> Storage in action. Recall <a href=canvas.html#halma>the Halma game we constructed in the canvas chapter</a>. There&#8217;s a small problem with the game: if you close the browser window mid-game, you&#8217;ll lose your progress. But with <abbr>HTML5</abbr> Storage, we can save the progress locally, within the browser itself. Here is <a href=examples/localstorage-halma.html>a live demonstration</a>. Make a few moves, then close the browser tab, then re-open it. If your browser supports <abbr>HTML5</abbr> Storage, the demonstration page should magically remember your exact position within the game, including the number of moves you&#8217;ve made, the position of each of the pieces on the board, and even whether a particular piece is selected.
<p>How does it work? Every time a change occurs within the game, we call this function:
<pre><code>function saveGameState() {
if (!supportsLocalStorage()) { return false; }
localStorage[""] = gGameInProgress;
for (var i = 0; i &lt; kNumPieces; i++) {
localStorage["halma.piece." + i + ".row"] = gPieces[i].row;
localStorage["halma.piece." + i + ".column"] = gPieces[i].column;
localStorage["halma.selectedpiece"] = gSelectedPieceIndex;
localStorage["halma.selectedpiecehasmoved"] = gSelectedPieceHasMoved;
localStorage["halma.movecount"] = gMoveCount;
return true;
<p>As you can see, it uses the <code>localStorage</code> object to save whether there is a game in progress (<code>gGameInProgress</code>, a Boolean). If so, it iterates through the pieces (<code>gPieces</code>, a JavaScript <code>Array</code>) and saves the row and column number of each piece. Then it saves some additional game state, including which piece is selected (<code>gSelectedPieceIndex</code>, an integer), whether the piece is in the middle of a potentially long series of hops (<code>gSelectedPieceHasMoved</code>, a Boolean), and the total number of moves made so far (<code>gMoveCount</code>, an integer).
<p>On page load, instead of automatically calling a <code>newGame()</code> function that would reset these variables to hard-coded values, we call a <code>resumeGame()</code> function instead. Using <abbr>HTML5</abbr> Storage, the <code>resumeGame()</code> function checks whether a state about a game-in-progress is stored locally. If so, it restores those values using the <code>localStorage</code> object.
<pre><code>function resumeGame() {
if (!supportsLocalStorage()) { return false; }
gGameInProgress = (localStorage[""] == "true");
if (!gGameInProgress) { return false; }
gPieces = new Array(kNumPieces);
for (var i = 0; i &lt; kNumPieces; i++) {
var row = parseInt(localStorage["halma.piece." + i + ".row"]);
var column = parseInt(localStorage["halma.piece." + i + ".column"]);
gPieces[i] = new Cell(row, column);
gNumPieces = kNumPieces;
gSelectedPieceIndex = parseInt(localStorage["halma.selectedpiece"]);
gSelectedPieceHasMoved = localStorage["halma.selectedpiecehasmoved"] == "true";
gMoveCount = parseInt(localStorage["halma.movecount"]);
return true;
<p>The most important part of this function is the caveat that I mentioned earlier in this chapter, which I&#8217;ll repeat here: <em>Data is stored as strings. If you are storing something other than a string, you&#8217;ll need to coerce it yourself when you retrieve it.</em> For example, the flag for whether there is a game in progress (<code>gGameInProgress</code>) is a Boolean. In the <code>saveGameState()</code> function, we just stored it and didn&#8217;t worry about the datatype:
<pre><code>localStorage[""] = <mark>gGameInProgress</mark>;</code></pre>
<p>But in the <code>resumeGame()</code> function, we need to treat the value we got from the local storage area as a string and manually construct the proper Boolean value ourselves:
<pre><code>gGameInProgress = (localStorage[""] <mark>== "true"</mark>);</code></pre>
<p>Similarly, the number of moves is stored in <code>gMoveCount</code> as an integer. In the <code>saveGameState()</code> function, we just stored it:
<pre><code>localStorage["halma.movecount"] = <mark>gMoveCount</mark>;</code></pre>
<p>But in the <code>resumeGame()</code> function, we need to coerce the value to an integer, using the <code>parseInt()</code> function built into JavaScript:
<pre><code>gMoveCount = <mark>parseInt</mark>(localStorage["halma.movecount"]);</code></pre>
<p class=a>&#x2767;
<h2 id=future>Beyond Named Key-Value Pairs: Competing Visions</h2>
<p>While <a href=#history>the past is littered with hacks and workarounds</a>, the present condition of <abbr>HTML5</abbr> Storage is surprisingly rosy. A new <abbr>API</abbr> has been standardized and implemented across all major browsers, platforms, and devices. As a web developer, that&#8217;s just not something you see every day, is it? But there is more to life than &#8220;5 megabytes of named key/value pairs,&#8221; and the future of persistent local storage is&hellip; how shall I put it&hellip; well, there are competing visions.
<p>One vision is an acronym that you probably know already: <abbr>SQL</abbr>. In 2007, Google launched <a href=>Gears</a>, an open source cross-browser plugin which included an embedded database based on <a href=>SQLite</a>. This early prototype later influenced the creation of the <a href=>Web SQL Database</a> specification. Web SQL Database (formerly known as &#8220;WebDB&#8221;) provides a thin wrapper around a <abbr>SQL</abbr> database, allowing you to do things like this from JavaScript:
<p class="legend top" style="padding-left:3em"><span class=arrow>&#x21b6;</span> actual working code in 4 browsers
<pre><code><mark>openDatabase</mark>('documents', '1.0', 'Local document storage', 5*1024*1024, function (db) {
db.changeVersion('', '1.0', function (t) {
t.<mark>executeSql</mark>('CREATE TABLE docids (id, name)');
}, error);
<p>As you can see, most of the action resides in the string you pass to the <code>executeSql</code> method. This string can be any supported <abbr>SQL</abbr> statement, including <code>SELECT</code>, <code>UPDATE</code>, <code>INSERT</code>, and <code>DELETE</code> statements. It&#8217;s just like backend database programming, except you&#8217;re doing it from JavaScript! Oh joy!
<p>The Web SQL Database specification has been implemented by four browsers and platforms.
<table class=bc>
<caption>Web SQL Database support</caption>
<tr><th title="Internet Explorer">IE<th title="Mozilla Firefox">Firefox<th title="Apple Safari">Safari<th title="Google Chrome">Chrome<th>Opera<th>iPhone<th>Android
<p>Of course, if you&#8217;ve used more than one database product in your life, you are aware that &#8220;<abbr>SQL</abbr>&#8221; is more of a marketing term than a hard-and-fast standard. (Some would say the same of &#8220;HTML5,&#8221; but never mind that.) Sure, there is an actual <abbr>SQL</abbr> specification (it&#8217;s called <a href="">SQL-92</a>), but there is no database server in the world that conforms to that and only that specification. There&#8217;s Oracle&#8217;s <abbr>SQL</abbr>, Microsoft&#8217;s <abbr>SQL</abbr>, MySQL&#8217;s <abbr>SQL</abbr>, PostgreSQL&#8217;s <abbr>SQL</abbr>, and SQLite&#8217;s <abbr>SQL</abbr>. Indeed, each of these products adds new <abbr>SQL</abbr> features over time, so even saying &#8220;SQLite&#8217;s <abbr>SQL</abbr>&#8221; is not sufficient to pin down exactly what you&#8217;re talking about. You need to say &#8220;the version of <abbr>SQL</abbr> that shipped with SQLite version X.Y.Z.&#8221;
<p>All of which brings us to the following disclaimer, currently residing at the top of the Web SQL Database specification:
<p>This specification has reached an impasse: all interested implementors have used the same SQL backend (Sqlite), but we need multiple independent implementations to proceed along a standardisation path. Until another implementor is interested in implementing this spec, the description of the SQL dialect has been left as simply a reference to Sqlite, which isn't acceptable for a standard.
<p>It is against this backdrop that I will introduce you to another competing vision for advanced, persistent, local storage for web applications: <a href=>the Indexed Database <abbr>API</abbr></a>, formerly known as &#8220;WebSimpleDB,&#8221; now affectionately known as &#8220;IndexedDB.&#8221;
<p>The Indexed Database <abbr>API</abbr> exposes what&#8217;s called an <em>object store</em>. An object store shares many concepts with a <abbr>SQL</abbr> database. There are &#8220;databases&#8221; with &#8220;records,&#8221; and each record has a set number of &#8220;fields.&#8221; Each field has a specific datatype, which is defined when the database is created. You can select a subset of records, then enumerate them with a &#8220;cursor.&#8221; Changes to the object store are handled within &#8220;transactions.&#8221;
<p>If you&#8217;ve done any <abbr>SQL</abbr> database programming, these terms probably sound familiar. The primary difference is that the object store has no structured query language. You don&#8217;t construct a statement like <code>"SELECT * from USERS where ACTIVE = 'Y'"</code>. Instead, you use methods provided by the object store to open a cursor on the database named &#8220;<code>USERS</code>,&#8221; enumerate through the records, filter out records for inactive users, and use accessor methods to get the values of each field in the remaining records. <a href="">An early walk-through of IndexedDB</a> is a good tutorial of how IndexedDB works, giving side-by-side comparisons of IndexedDB and Web SQL Database.
<p>At time of writing, IndexedDB has only been implemented in <a href=>a beta version of Firefox 4</a>. (By contrast, Mozilla has stated that <a href="">they will never implement Web SQL Database</a>.) Google has stated that <a href="">they are considering IndexedDB support</a> for Chromium and Google Chrome. And even Microsoft has said that IndexedDB &#8220;<a href="">is a great solution for the web</a>.&#8221;
<p>So what can you, as a web developer, do with IndexedDB? At the moment, virtually nothing beyond some technology demos. A year from now? Maybe something. Check the &#8220;Further Reading&#8221; section for links to some good tutorials to get you started.
<p class=a>&#x2767;
<h2 id=further-reading>Further Reading</h2>
<p><abbr>HTML5</abbr> storage:
<li><a href=""><abbr>HTML5</abbr> Storage</a> specification
<li><a href="">Introduction to DOM Storage</a> on <abbr>MSDN</abbr>
<li><a href="">Web Storage: easier, more powerful client-side data storage</a> on Opera Developer Community
<li><a href="">DOM Storage</a> on Mozilla Developer Center. (Note: most of this page is devoted to Firefox&#8217;s prototype implementation of a <code>globalStorage</code> object, a non-standard precursor to <code>localStorage</code>. Mozilla added support for the standard <code>localStorage</code> interface in Firefox 3.5.)
<li><a href="">Unlock local storage for mobile Web applications with HTML5</a>, a tutorial on IBM DeveloperWorks
<p>Early work by Brad Neuberg <i>et. al.</i> (pre-<abbr>HTML5</abbr>):
<li><a href="">Internet Explorer Has Native Support for Persistence?!?!</a> (about the <code>userData</code> object in IE)
<li><a href="">Dojo Storage</a>, part of a larger tutorial about the (now-defunct) Dojo Offline library
<li><a href=""><code></code> <abbr>API</abbr> reference</a>
<li><a href=""></a> Subversion repository
<p>Web SQL Database:
<li><a href="">Web SQL Database</a> specification
<li><a href="">Introducing Web <abbr>SQL</abbr> Databases</a>
<li><a href="">Web Database demonstration</a>
<lI><a href="">persistence.js</a>, an &#8220;asynchronous JavaScript <abbr>ORM</abbr>&#8221; built on top of Web <abbr>SQL</abbr> Database and Gears
<li><a href="">Indexed Database <abbr>API</abbr></a> specification
<li><a href="">Beyond HTML5: Database APIs and the Road to IndexedDB</a>
<li><a href="">Firefox 4: An early walk-through of IndexedDB</a>
<p class=a>&#x2767;
<p>This has been &#8220;The Past, Present &amp; Future of Local Storage for Web Applications.&#8221; The <a href=table-of-contents.html>full table of contents</a> has more if you&#8217;d like to keep reading.
<div class=pf>
<h4>Did You Know?</h4>
<div class=moneybags>
<blockquote><p>In association with Google Press, O&#8217;Reilly is distributing this book in a variety of formats, including paper, ePub, Mobi, and <abbr>DRM</abbr>-free <abbr>PDF</abbr>. The paid edition is called &#8220;HTML5: Up &amp; Running,&#8221; and it is available now. This chapter is included in the paid edition.
<p>If you liked this chapter and want to show your appreciation, you can <a href=";tag=diveintomark-20&amp;creativeASIN=0596806027">buy &#8220;HTML5: Up &amp; Running&#8221; with this affiliate link</a> or <a href=>buy an electronic edition directly from O&#8217;Reilly</a>. You&#8217;ll get a book, and I&#8217;ll get a buck. I do not currently accept direct donations.
<p class=c>Copyright MMIX&ndash;MMXI <a href=about.html>Mark Pilgrim</a>
<form action=><div><input type=hidden name=cx value=014021643941856155761:6jgee_nxreo><input type=hidden name=ie value=UTF-8><input type=search name=q size=25 placeholder="powered by Google&trade;">&nbsp;<input type=submit name=sa value=Search></div></form>
<script src=j/jquery.js></script>
<script src=j/modernizr.js></script>
<script src=j/dih5.js></script>