Using Web Storage on the Client-Side

Robert Nyman | July 28, 2010

 

A lot of useful things have been offered to web developers in the HTML5 APIs and related technologies and one of them is being able to store information on the client-side. Our only approach before was scripting with cookies, and really, that situation was far from optimal. There have never been any easy properties or methods for using cookies to handle data and instead we have had to resort to nasty string-handling routines to make it do something remotely close to what we wanted. Another major downside has been the size limit of cookies which has generally been 4096 bytes, and that is not a lot of space. Therefore, we were bound to move on.

Introducing localStorage and sessionStorage

With the new Web Storage specification we now have new, beefed-up options with localStorage and sessionStorage, which utilize the same syntax but have different use cases, and the total storage space available is (at least) 5 MB! The localStorage object is for more permanent storage; the user can close the web browser, start over another day and all the information will already be there - all connected to the current domain context you are in. The only way to discard it is either programmatically, or if the user clear their cache. Whereas localStorage is for storage over longer periods of time, sessionStorage is exactly what the name implies: storing for that specific session, but cleared as soon as the session is terminated. Unfortunately, as it is right now, it is only through various web browser development tools that you can actually examine what has been stored, which makes it a bit too hidden away from less tech-savvy users.

The good thing is that Web Storage is supported by all current versions of the major web browsers including:

  • Internet Explorer 8+
  • Firefox 3.5+
  • Google Chrome 4+
  • Safari 4+
  • Opera 10.5+

It’s also supported on certain mobile phones using iPhone OS 2 and Android 2

Coding away

So, let's take a look at some basic code to get us started. I will use localStorage for my examples here, but everything mentioned below goes for sessionStorage as well. We'll start by storing my name in the localStorage object:

localStorage.setItem("name", "Robert");

That's pretty easy, right? Compare that to the cookie handling that would have been needed. Reading out that value is done like this:

localStorage.getItem("name");

And then, of course, when we're sick of Robert, we are free to remove that value:

localStorage.removeItem("name");

Or remove all saved values altogether:

localStorage.clear();

Storing more than text

As you have seen above, everything we saved so far was in a text format. But naturally, there are cases where we'd want to save something a little more complex. Per the specification, localStorage and sessionStorage only support saving text, but using our clever minds, we can use JSON to do more advanced operations. Luckily, there is native JSON support available in all the web browsers that support Web Storage, so let's use that! In this example, we first create a simple JavaScript object and then use the stringify method of the JSON object to save it as a string in localStorage. Then, when we read out that stored value, we resort to the parse method of the JSON object to turn into a JavaScript object again.

// Using JSON
var info = {
    "language" : "Swedish",
    "occupation" : "Web Developer"
};
    
// Save as string
localStorage.setItem("info", JSON.stringify(info));
    
// Load as JSON object
console.log(JSON.parse(localStorage.info));

A little example

Let’s say you are building a webmail client of some sort. Naturally you want dynamic page updating of just some certain parts with Ajax when you, for instance, read a message. What’s important, then, is that if the user has opened a message you want to store that information to just retrieve it from localStorage instead of contacting the server again.

Here’s an example that gets all the elements in a page with the class name “example” and reads out the data for each specific message and then stores it in localStorage. This example doesn’t use any JavaScript library to be able to have all the code presented here, but naturally you could achieve this with your favorite JavaScript library as well.

HTML Code

<div id="unique-message-id-1" class="message">
    <dl>
        <dt>From</dt>
        <dd class="sender">bill@microsoft.com</dd>
        <dt>To</dt>
        <dd class="recipients">robert@robertnyman.com</dd>
        <dt>Date</dt>
        <dd class="date">Mon, Jun 14, 2010 at 2:23 AM</dd>
        <dt>Subject</dt>
        <dd class="subject">Interested in running a company?</dd>
        <dt>Message</dt>
        <dd class="message-content">
            Hi Robert, I wonder if you would like to run a major company?
        </dd>
    </dl>
</div>

<div id="unique-message-id-2" class="message">
    <dl>
        <dt>From</dt>
        <dd class="sender">robert@robertnyman.com</dd>
        <dt>To</dt>
        <dd class="recipients">bill@microsoft.com</dd>
        <dt>Date</dt>
        <dd class="date">Mon, Jun 14, 2010 at 3:42 AM</dd>
        <dt>Subject</dt>
        <dd class="subject">RE: Interested in running a company?</dd>
        <dt>Message</dt>
        <dd class="message-content">
            Hi Bill, Thanks for the offer, but that's not really my thing.
        </dd>
    </dl>
</div>

JavaScript code

/*
    getElementsByClassName developed by Robert Nyman, https://robertnyman.com
    Code/licensing: https://code.google.com/p/getelementsbyclassname/
*/
var getElementsByClassName = function (className, tag, elm){
    if (document.getElementsByClassName) {
        getElementsByClassName = function (className, tag, elm) {
            elm = elm || document;
            var elements = elm.getElementsByClassName(className),
                nodeName = (tag)? new RegExp("\\b" + tag + "\\b", "i") : null,
                returnElements = [],
                current;
            for(var i=0, il=elements.length; i<il; i+=1){
                current = elements[i];
                if(!nodeName || nodeName.test(current.nodeName)) {
                    returnElements.push(current);
                }
            }
            return returnElements;
        };
    }
    else if (document.evaluate) {
        getElementsByClassName = function (className, tag, elm) {
            tag = tag || "*";
            elm = elm || document;
            var classes = className.split(" "),
                classesToCheck = "",
                xhtmlNamespace = "https://www.w3.org/1999/xhtml",
                namespaceResolver = (document.documentElement.namespaceURI === xhtmlNamespace)? xhtmlNamespace : null,
                returnElements = [],
                elements,
                node;
            for(var j=0, jl=classes.length; j<jl; j+=1){
                classesToCheck += "[contains(concat(' ', @class, ' '), ' " + classes[j] + " ')]";
            }
            try {
                elements = document.evaluate(".//" + tag + classesToCheck, elm, namespaceResolver, 0, null);
            }
            catch (e) {
                elements = document.evaluate(".//" + tag + classesToCheck, elm, null, 0, null);
            }
            while ((node = elements.iterateNext())) {
                returnElements.push(node);
            }
            return returnElements;
        };
    }
    else {
        getElementsByClassName = function (className, tag, elm) {
            tag = tag || "*";
            elm = elm || document;
            var classes = className.split(" "),
                classesToCheck = [],
                elements = (tag === "*" && elm.all)? elm.all : elm.getElementsByTagName(tag),
                current,
                returnElements = [],
                match;
            for(var k=0, kl=classes.length; k<kl; k+=1){
                classesToCheck.push(new RegExp("(^|\\s)" + classes[k] + "(\\s|$)"));
            }
            for(var l=0, ll=elements.length; l<ll; l+=1){
                current = elements[l];
                match = false;
                for(var m=0, ml=classesToCheck.length; m<ml; m+=1){
                    match = classesToCheck[m].test(current.className);
                    if (!match) {
                        break;
                    }
                }
                if (match) {
                    returnElements.push(current);
                }
            }

            return returnElements;
        };
    }
    return getElementsByClassName(className, tag, elm);
};

window.onload = function () {
    // Creates a JavaScript object for messages, and selects elements with a "message" class
    var messagesToSave = {},
        messages = getElementsByClassName("message");
        
    // Iterates over all messages in the page, and gets their data  
    for (var i=0, il=messages.length, message; i<il; i++) {
        message = messages[i];
        messagesToSave[message.id] = {
            "from" : getElementsByClassName("sender", "dd", message)[0].innerHTML,
            "to" : getElementsByClassName("recipients", "dd", message)[0].innerHTML,
            "date" : getElementsByClassName("date", "dd", message)[0].innerHTML,
            "subject" : getElementsByClassName("subject", "dd", message)[0].innerHTML,
            "message" : getElementsByClassName("message-content", "dd", message)[0].innerHTML
        };
    };
    
    // Turns that data into a string and stores it with localStorage
    localStorage.setItem("messages", JSON.stringify(messagesToSave));
    
    // Reads out the stored data and turns into a JavaScript object
    console.log(JSON.parse(localStorage.getItem("messages")));
};

What about older web browsers?

Sure, all the latest web browsers support Web Storage, and older web browsers are, mostly, being phased out pretty fast nowadays. But without a doubt you still need to cater to Internet Explorer 7, and perhaps Internet Explorer 6 as well, so what should we do about them?

Technically, I won’t delve into how they work, but a number of smart people have looked into how to provide alternatives for those situations. Some of these solutions are:

Alternative storage on the web

There have been some attempts to complement Web Storage with some more advanced database handling, more specifically with Web SQL Database and Indexed Database API. As it stands right now, Google Chrome, Safari and Opera have implemented the Web SQL Database API, whereas Microsoft and Firefox have made statements that they will support IndexedDB, an alternative specification proposed by Oracle. While the Indexed Database API hasn't been implemented in any officially released web browser at the time of writing this article, Mozilla are working on it, Microsoft seems to support it and Google have expressed interest in it. So, only the future can tell, but my guess is that browser vendors will support the Indexed Database API as the database API for the web.

A new world of storing!

I hope this introduction to Web Storage has inspired you to explore this new territory and that it can aid you in building even better web applications and web sites!

 

About the Author

Robert has been working with web developing, mostly interface coding, since 1998. His biggest interests lie in HTML, CSS and JavaScript, where especially JavaScript has been a love for quite some time. He regularly blogs at https://robertnyman.com about web developing, and is running/partaking in a number of open source projects.

Find Robert on: