Try Tuts+ Premium, Get Cash Back!
Browser Storage for HTML5 Apps

Browser Storage for HTML5 Apps

Tutorial Details
  • Technology: HTML5
  • Difficulty: Beginner
  • Completion Time: 20 - 35 Minutes

For years one of the main advantages of writing desktop applications has been the easy access to local storage on the client machine. Web developers have put up with the inability to store and retrieve data from the client’s machine for a long time, but it looks like that may change soon. You might even think it already has after reading this article. Yes, I’m going to discuss the origins of persisting data on clients machine and then introduce you to the Web Storage standard.

Most web developers know that the only kind of local storage we could expect from a web browser comes in the form of cookies. Well, not entirely. The desire to store data on the client machine is not a new concept and it wasn’t conceived while creating HTML5 specifications. What is even more surprising is that a working implementation was developed by Microsoft as part of the IE6 feature set. They called it userData and it essentially guaranteed at least 640KB of local space per domain depending on the IE security policies set by the user. That may seem like very little space by today’s standard, but when we compare it to the maximum 4KB space available to us by Cookies, the improvement is appreciable.

What’s Wrong With Cookies?

A number of things. The foremost problem with Cookies is that they are sent back and forth between browser and server with every HTTP request. This isn’t a desirable behaviour because more often than not developers do not wish to transmit local data to the server more than once, if once at all. Cookies give developer no choice.

As we said previously, Cookies can only store up to 4KB of data. It’s not a lot of data, nevertheless 4KB is enough data to noticeably slow down page requests.

Also, Cookies are passed back and forth between the client and server in clear text. Therefore, the only way to protect them is by encrypting the data while communicating with the backend server using SSL (Secure Socket Layer). However, most websites on the internet don’t use SSL, which leaves storage open to eavesdropping.

There are other issues that make Cookies less useful. Ideally, developers wish to have the ability to persist large amounts of data on the client machine and not have to transmit that data to the server over and over again.

What Are The Alternative Solutions?

So far we have not discussed non-standard workarounds for persisting data on the client machine. When Adobe (then known as Macromedia) developers were releasing Flash Player 6, they too had to address the same problem. In 2002, Flash Player 6 introduced a new feature called Local Shared Object or more often known as Flash Cookies to effectively introduce the same capabilities as standard HTTP Cookies to Flash movies and sites. Local Shared Object allowed developers to persist up 100KB of data onto the client machine by default.

The second solution is Googleís implementation of Local Storage as part of the Gears plugin for Web Browsers. Gears was (and I tell you why I use was in a moment) the combination of multiple missing and useful features needed for developing Rich Internet Applications (RIA). Gears’ Local Storage was based on the less popular Web SQL specification that took advantage of SQLite. You guessed it right, Gears gave developers a full blown SQL database for persisting an unlimited amount of data on the client machine.

The developers of Ajax Massive Storage System (AMASS) took this opportunity and developed a third party JavaScript library that made it possible for standard HTML sites to take advantage of the Local Shared Object feature in Flash or the Gears plugin in order to persist data on the client machine. Also, Dojo JavaScript Library is capable of detecting the availability of Local Storage mechanics (e.g. Google Gears, Local Shared Object. etc.) and provides a unified interface for persisting data across different Web Browsers.

Back to why I said Gears ‘was’ instead of ‘still is’: that’s because Google recently announced that they will be dropping further development of the Gears plugin in favor of HTML5 and the Web Storage specification presented in this tutorial.

HTML5 and Web Storage Specifications

Now that the history lesson is over, we are going to learn about Web Storage and dive into some code to better understand it. The easiest way to describe Web Storage is the ability to persist data on the client machine in the form of one key for one value. This is very similar to how associative arrays are used:

    { "The Key" : "The Value" }

Local Storage is designed to be natively supported by Web Browsers. This means no more third party libraries and messing with Flash. Surprisingly, Web Storage has been one of the more successful specifications in terms of adoption by modern browsers. In fact, almost all modern browsers support Web Storage, including:

  • Internet Explorer 8+
  • Firefox 3.5+
  • Safari 4+
  • Opera 10.5+
  • iPhone Safari
  • Android Web Browser

While Web Storage aims to provide functionality similar to Cookies, it has been further refined to not carry any of their negative attributes. For instance, Web Storage allows for persisting up to 5MB of data, a significant increase in space compared to how much data can be stored in a Cookie. Also, persisting data using Web Storage will not result in sending that data to the server backend with every page request. This significantly increases performance. According to the Web Storage specification, Web Browsers only expire the persisted data from the local machine when requested to do so by the user and will always avoid deleting data while a script that could access that data is running.

Web Browsers expose Web Storage through the localStorage object in JavaScript. One easy way to determine whether a Web Browser can support Web Storage is to execute this JavaScript code:

    var webStorageSupported = ('localStorage' in window) && window['localStorage'] !== null;

According to the W3C’s specification for Web Storage, the localStorage object implements the following set of methods and properties from the Storage interface. Let’s examine each of these methods and properties to find out what they do and how they can be used:

interface Storage {
    readonly long length;
    void setItem(String key, Object data);
    Object getItem(String key);
    void removeItem(String key);
    void clear();
    String key(long index);
};

The length property is very useful. It will return the number of key/value pairs currently saved to Local Storage under the currently accessed domain:

    alert(localStorage.length);

If no key/value pairs have previously been saved in the local storage, then the above script will display an alert window with “0″ as the message, otherwise the message will be the number of persisted key/value pairs.

The setItem(key, value) method simply saves a new entry on the local machine. To save the key name with the value arman we could execute this script:

    localStorage.setItem('name', 'arman');

To ensure that key name was truly saved to the local storage with the value arman we need to use the getItem(key) method. The getItem method simply accepts a key and searches the local storage to find a matching key and then returns its value.

    localStorage.setItem('name', 'arman');
    var value = localStorage.getItem('name');
    alert(value);

If you run the above script you should see an alert window containing the word arman appear on the screen, confirming that we’ve successfully saved a new key/value pair to the local storage. Since the localStorage object behaves similar to associative arrays, we could simplify the above script to look like this and it will still function just the same:

    localStorage['name'] = 'arman';
    var value = localStorage['name'];
    alert(value);

Let’s look at the removeItem(key) method. This method is designed to remove a previously saved key/value pair from the local storage. If the key does not exist, this method simply does nothing. The following code sample demonstrates the use of the removeItem method:

    localStorage.setItem('name', 'arman');
    localStorage.removeItem('name');
    var value = localStorage.getItem('name');
    alert(value);

When the above script is executed, you should see an alert window with the value null in the alert box. Using the name key, the above script simply creates a new key/value pair and immediately removes it from the local storage. Subsequently, a null value is returned when accessing the local storage with the same name key.

There will come occasions where there will be a need to completely clear the local storage and start with a clean slate. The clear() method is designed exactly for that purpose. This method automatically empties all the previously saved key/value pairs from the local storage. If there are no entries then nothing will change.

    localStorage.setItem('name', 'arman');
    localStorage.setItem('name', 'smith');
    localStorage.setItem('name', 'frank');
    alert(localStorage.length);
    localStorage.clear();
    alert(localStorage.length);

Although the above script creates three new key/value pairs (as evidenced by the first alert), the call to the clear() method removes all the entries. Subsequently, the second alert window will display a “0″ message.

The final method we need to look at is the key(index) method. This method will retrieve the name of a key based on the index parameter. localStorage maintains a 0 based list of all entries within itself. Therefore to access the first key from the local storage, we need to use 0 as the index as illustrated in this script:

    localStorage.clear();
    localStorage.setItem('age', 5);
    alert(localStorage.key(0));

When the above script is executed it should display an alert window with the message “age”. Note how in the above example the first line of code clears the local storage. This is to ensure we begin with a clean slate. Another useful application of the key() method is in conjunction with the length property. For instance to get all the key/value pairs from the local storage without knowing the keys in advance, we could write a script like the following:

    localStorage.clear();
    localStorage.setItem("title", "Mr.");
    localStorage.setItem("fullname", "Aaron Darwin");
    localStorage.setItem("age", 17);
    localStorage.setItem("height", 182.5);
    for(var i = 0; i < localStorage.length; i++)
    {
        var keyName = localStorage.key(i);
        var value = localStorage.getItem(keyName);
        alert(keyName + " = " + value);
    }

In the above script, our code first clears and then adds four new key/value pairs to the local storage. Then it uses the length property in a For loop to work out the key for each key/value pair. On every iteration of the loop the key is assigned to the keyName variable which is then passed to the getItem() method to retrieve its value.

The Subtleties

When accessing data using a key that doesn't exist in the local storage, instead of an exception, a null value is always returned. This makes it difficult to know if the value of the key is null or the key simply doesn't exist in the local storage.

The second one to talk about is the setItem(key, value) method. We know that we can pass any type of value to the setItem() method for the value parameter, but that isn't entirely true. In JavaScript, we are able to create Image objects. However, the current implementation of Web Storage only allows persisting primitive types such as String, Boolean, Integer and Float types. Therefore, we can expect the following script to throw an exception and crash:

    var imageObject = new Image("http://www.google.com/logo.gif");
    localStorage.setItem("image", imageObject);

Additionally, even though we can save Boolean, Integer and Float types, the underlying representation of these types falls back onto the String type. This means regardless of the type of value passed to the setItem() method, the getItem() method will always return a value that is of type String. Let's look at an example script:

    var integerVariable = 34;
    localStorage.setItem("age", integerVariable);
    var newVariable = localStorage.getItem("age");
    alert(typeof(newVariable) == "number");

In the above script, ultimately the value of the integerVariable is saved as String. Therefore when we inspect the type of newVariable after getting its value from the local storage, it's no longer an Integer type and therefore the comparison statement will evaluate to false. In such situations, we must use the handy parseInt(String) and parseFloat(String) functions for conversion. There is NO function to parse Boolean data type so we must simply compare the retrieved value with "true" or "false" strings.

Finally, to get the comparison statement in the above script to evaluate to true, we must modify our code to use the parseInt() function as shown in the following script:

    var integerVariable = 34;
    localStorage.setItem("age", integerVariable);
    var newVariable = parseInt(localStorage.getItem("age"));
    alert(typeof(newVariable) == "number");

Now, when the above script is executed, the type of the value stored in the newVariable will be Integer. Subsequently, the comparison statement will evaluate to true.

When the persisting data reaches the 5MB quota, Web Browsers do not provide the ability to ask for more space in the local storage. They instead throw the QUOTA_EXCEEDED_ERR exception to notify the script that there is no more space. The simplest way to handle this exception is to catch the exception and notify the user of the problem.

Alternatively when catching the exception, the script can delete some key/value entries from the local storage to clear up space for new pairs.

Lastly, when the document is requested directly from the disk and not from a web server, the local storage will be cleared every time you navigate away from the page.

Web Storage Events

Web Storage Standard allows scripts to be notified when other parts of the code add, update or delete entries from the local storage. Thus, whenever something changes in the local storage, a Storage Event is going to fire. However, in the case of deleting an entry using a non existing key, no event will be fired. This is because nothing will change in the local storage. Unfortunately, my tests proved that currently Webkit browsers (such as Safari and Chrome) do not fire the Storage Events as described by the Web Storage specification. Internet Explorer, Firefox and Opera browsers do behave as expected.

A local storage event cannot be cancelled. Its sole purpose is to notify the user code of a change that has happened inside the local storage.

The following script shows you how to listen for changes in the local storage, by registering an event handler which will be called every time a Storage Event is fired:

    if (window.addEventListener) {
        window.addEventListener("storage", handleStorageChange, false);
    }
    else
    {
        window.attachEvent("onstorage", handleStorageChange);
    }
    function handleStorageChange(event)
    {
        alert("Something was changed in the local storage");
    }

Since no version of Internet Explorer (except for version 9 public preview) supports the DOM Level 2 event handling system, the attacheEvent() method has to be used to register event handlers.

The event argument is useful because it carries information about changes in the local storage. It adheres to the StorageEvent interface:

interface StorageEvent : Event {
    readonly String key;
    readonly Object oldValue;
    readonly object newValue;
    readonly String url;
};

The key property identifies which key/value pair was changed. The oldValue and the newValue properties behave as their names suggest. That is, they respectively contain the previous and the new value for the key.

Finally, the url property contains the address of the document for which the local storage was just modified. However, due to multiple implementation iterations of the Web Storage specification, in some Web Browsers the url property may be implemented as uri. Therefore it's better to first check the url property and then uri in case the url property is undefined.

Demo

After reading this much, I usually like to see an example before I believe it all. Just like myself, I'm sure many of you would also like to see a working example. That's no problem, because I've prepared a small script that simply demonstrates the use of the web storage standard in a working application.

It's made such that it can be also be tested using iPhone Safari and the Android Web Browser. The idea is to have the browser remember which boxes have been opened before using the local storage. I'll leave you with this code to play with:

<!DOCTYPE html>
<html>
    <head>
        <meta name="viewport" content="width=240,user-scalable=no" />
        <style>
            * { padding: 0px; margin: 0px; font-family: "Lucida Sans", "Tahoma", san-serif, arial; }
            body { background: #333; }
            h1 {  font-size: 20px;  }
            p { padding: 5px; margin-bottom: 10px; font-size: 12px; }
            button { padding: 4px; margin-top: 10px; }
            #wrap { width: 240px; background: #FFF; margin: 0px auto; padding: 10px; }
            #blocks .unchecked, a { height: 40px; width: 40px; margin: 4px; display: inline-block; cursor: pointer; text-align: center; line-height: 40px; }
            #blocks .unchecked { background: #000; }
            #blocks .unchecked:hover { background: #333; }
        </style>
        <script>
            var colorTable = {
                "0" : "blue",
                "1" : "red",
                "2" : "green",
                "3" : "orange",
                "4" : "purple",
                "5" : "brown",
                "6" : "gold",
                "7" : "lime",
                "8" : "lightblue",
                "9" : "yellow"
            };
            function initializeGame()
            {
                if (browserSupportsLocalStorage() == false)
                {
                    alert("This browser doesn't support Web Storage standard");
                    return;
                }
                var containerDiv = document.getElementById("blocks");
                for (var i = 0; i < 10; i++)
                {
                    var id           = i.toString();
                    var anchor       = document.createElement("a");
                    anchor.id        = id;
                    anchor.innerHTML = i + 1;
                    if (localStorage.getItem(id) != null)
                    {
                        anchor.style.backgroundColor = colorTable[id];
                    }
                    else
                    {
                        anchor.className = "unchecked";
                        if (anchor.addEventListener)
                        {
                           anchor.addEventListener("click", handleBoxClick, false);
                        }
                        else
                        {
                           anchor.attachEvent("onclick", handleBoxClick);
                        }
                    }
                    containerDiv.appendChild(anchor);
                }
            }
            function handleBoxClick(e)
            {
                var target = (e.target) ? e.target : e.srcElement;
                if (target.className == "")
                    return;
                var id = target.id;
                var color = colorTable[id]
                target.className = "";
                target.style.backgroundColor = colorTable[id];
                localStorage.setItem(id, color);
            }
            function resetGame()
            {
                var containerDiv = document.getElementById("blocks");
                containerDiv.innerHTML = "";
                localStorage.clear();
                initializeGame();
            }
            function browserSupportsLocalStorage()
            {
                return ('localStorage' in window) && (window['localStorage'] != null);
            }
        </script>
    </head>
    <body onload="initializeGame();">
        <div id="wrap">
            <h1>Web Storage Demo</h1>
            <p>
                This page was design to simply demonstrate the use of Web Storage in modern browsers. In this example boxes
                that haven't yet been clicked remain black. Once you click a black box, it's real color will be revealed.
                The browser however will remember which boxes are clicked, even when you navigate away from this page.
                Go on, give it a try.
            </p>
            <div id="blocks">
            </div>
            <button onclick="resetGame()">Reset Game</button>
            <button onclick="document.location.reload(true);">Refresh Page</button>
        </div>
    </body>
</html>

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • http://adrusi.com/ adrian

    Interesting, both nettuts and mobiletuts published a tutorial on localstorage. And by the way, localstorage isn’t HTML5, it was given its own spec because webSQL and localstorage were too complex to be a subspec

    • http://mobile.tutsplus.com Mark Hammonds

      Adrian,

      Mea Culpa on the technical inaccuracy in the original title! My intention was to convey the idea of teaching this topic for use with an HTML5 App, not necessary as a core aspect of HTML itself. I’ve updated the title to more accurately convey that intention. Thanks for pointing this out!

      Best Regards,
      Mark Hammonds

  • Omo Olorun

    anchor.id = id; should be corrected. typo

    • Arman Mirkazemi
      Author

      I checked the code, its correct but the syntax highlighter somehow adds an extra ‘i’ there for no reason. Sorry about this.

  • http://www.cancelbubble.com cancel bubble

    “var webStorageSupported = (‘localStorage’ in window) && window['localStorage'] !== null;”

    I thought that generally, the in operator should be avoided? Wouldn’t this be a simpler test:

    var webStorageSupported;

    (typeof window.localStorage !== ‘undefined’)
    ? webStorageSupported = true
    : webStorageSupported = false;

    • http://codecanyon.net/user/DenonStudio/portfolio Arman Mirkazemi
      Author

      The problem with the “in” operator is that it does not only check if the array contains the key in question, it also checks for functions on the prototype. For instance using the “in” operator to check for “map” and “slice” keys on an empty array will return true when ideally it should return false.

      However in the above statement we know that localStorage is an object, not a function. Therefore our code is safe to execute. Personally I prefer the “in” operator even though it can bring about tricky situation like when negating it or the problem I mentioned earlier. I guess it should be used with a little care and good judgment.

      Your suggestion however should work as well.

  • puran

    good one..special thanks for the demo

  • http://www.shopifyconcierge.com Gavin

    LocalStorage is a great step forward.

    One thing I didn’t see was the equivalent of cookie’s “expires”. IOW, there doesn’t appear to be a way to say when the data should be removed by the user agent. I guess that means you are responsible for managing your own data, but I don’t see how you could do it unless the user returns to your site.

    • Arman Mirkazemi
      Author

      The initial analysis also made me think of this same problem, but the more I’ve used local storage the more realized that it wasn’t designed to replace Cookies. In fact we’ve never had anything like Local Storage before. All along we have tried to use Cookies like local storage, but Cookies were never intended to be used as a permanent local storage on the client side.

      My general conclusion was to stop thinking of Local Storage as Cookies 2.0 and think of it as a permanent storage on the client machine. You don’t have to use it, but if you want to, it’s really easy to use.

      • http://www.shopifyconcierge.com Gavin

        > think of it as a permanent storage on the client machine

        Well, it is certainly permanent if your stuffs aren’t deleted :-)

        Seems like a gaping hole in the spec to me. That, plus a complete lack of security.

      • Arman Mirkazemi
        Author

        > Seems like a gaping hole in the spec to me. That, plus a complete lack of security.

        Gavin, I’m certainly not trying to convince you that Web Storage is the way to go. However if the data is too important to just sit inside local storage in clear text then one could always have it stored encrypted. Desktop apps certainly maintain their local data either in binary format or in some kind of encrypted form.

      • http://www.shopifyconcierge.com Gavin

        Hi Arman,

        I should say first thank you for a very good tutorial. I think there are certain scenarios where Local Storage could definitely be useful, so no need to convince me of this. Hopefully readers of this tutorial would find this discussion useful in terms of highlighting things to consider when you use the technology.

        Re your comments on encryption, I think ideally this would be done at the browser level, which to me is safer than using a JS solution and would also prevent MITM attacks. What I was actually thinking about though was more along the lines of malicious sites being able to replace values in local storage if they know the key other sites are using apriori. I know there have been some efforts along these lines: http://www.nczonline.net/blog/2010/04/13/towards-more-secure-client-side-data-storage/

        Thanks again, Gavin.

  • http://www.cancelbubble.com cancel bubble

    Couldn’t you just store an object where one of the properties is expires:*timestamp* and then periodically compare the timestamp with the current time. Then delete that object when appropriate.

    • http://www.shopifyconcierge.com Gavin

      Yes, that was the idea I started thinking about, but then I wondered how I could compare timestamps if the user isn’t on my site?

  • http://www.cancelbubble.com cancel bubble

    Good point, if the user never comes back it would always persist (unless the user purged it).

  • zxk

    good!
    But after execute this script “var webStorageSupported = (‘localStorage’ in window) && window['localStorage'] !== null;” webStorageSupported ‘s value is “undefined” in IE8 on my computer
    my OS: Window Sp2

    • http://www.facebook.com/rajeshskyler Rajeshkumar Pandidurai

      you need to set your browser to be in IE8 mode..

  • Lee

    Does this method work for iPhone apps? (I mean if it’s in the HTML files?) I have mine pulling some web data (HTML/Java/CSS iWebkit) that will only be updated a few times a year and I would love the other static pages be stored into the iPhone so they can be viewed offline.

  • http://thanks Haiyal

    thanks for such good explanation

  • satya

    I might have missed it but do you have a link I can point my browser to test your demoa.

  • http://www.facebook.com/rajeshskyler Rajeshkumar Pandidurai

    can you specify any real time example where we can use indexedDB???