Posted by eelzinaty on 9th August 2011

HTML5: TO DO Application Using Local Storage

In web application when a developer wants to store any thing for user to use it again, he thinks of two ways: one is the client side and the other is the server side. The server storage is a good solution if we think about large data but if we want our application to work offline there is no way to manipulate server data without using client side storage.

So, for small amount of data that needs to stay at the client side where poor solutions are present like cookies, userData – IE special object, Google gears. The last two are browser specific which means they will not work in any browser, while cookies work in all browsers it suffers from serious limitations like:

  1. Small size, up to 4kb per cookie and 20 cookies accepted from a particular server or domain.
  2. It may be cleaned by user.
  3. Consumes bandwidth, because it is included in every request header.

Fortunately, HTML5 comes with a new specification called Web Storage that supports storage of data along one session or beyond, that one used to store data for one session called sessionStorage, and the other one called localStorage which is our concerns in this tutorial.

About localStorage

This is a new HTML magic element that allow developer to store domain-specific data at client side and manipulate this data without the need of server storage, it can store up to 5 MB of data without the fear of accidentally clearing it like cookies, and for security issues there is no way to access other domain-specific storage even if it is a sub-domain of the current website. Currently, it is supported by IE 8+, FF 3.5+, Chrome 4+. It uses key/value pair for storing elements, with only string support for the value, so if we need to store something other than string we first need to cast the value before using it. In addition to that, it doesn’t transmit values to the server with every request as cookies do, nor does the data in a local storage area ever expire.

Support Detection

We can easily detect if browser supports localStorage or not by using this code:

try {
    return 'localStorage' in window && window['localStorage'] !== null;
} catch (e) {
    return false;
}

localStorgae Functions

localStorage object supports the necessary functions needed by developers to build an application that depends on client data storage, and here is a list of the functions supported with a brief description about each one:

  1. clear
    Used to clear all key/value pairs stored in the storage.

    localStorage.clear();
  2. getItem(key)
    Used to get value from the storage by key.

    localStorage.getItem(key);
  3. key(index)
    Used to get storage key by index.

    for(var i = 0; i < localStorage.length; i++)
        localStorage.key(i);
    
  4. length
    Used to get the number of key/value pairs in storage.

    localStorage.length;
  5. remainingSpace – IE 8 only
    Used to get the remaining space in bytes.

    localStorage.remainingSpace;
  6. removeItem(key)
    Used to remove a value from storage using its key.

    localStorage.removeItem(key);
  7. setItem(key, value)
    Used to insert new or update existing value to storage.

    localStorage.setItem(key,value);

localStorage Event

There is only one event supported called storage which fired for each set or remove call and also fired with clear function, this event is not fired in the the page made the event, we cannot also stop the action occurred from this event. localStorage event object supports the following properties:

  1. key
    Stores the key of the new, updated or deleted item from the storage and it is null if we called clear function.
  2. newValue
    Stores the new value to be added or updated on the storage.
  3. oldValue
    Stores the old value of updated or removed item.
  4. storageArea
    Gets the storage object of the affected document.
  5. url
    Stores the the fully qualified URL of the document that fired the event.

Note: all the above properties except url are not supported in IE 8, also we can’t use jQuery bind method to attach storage event.

And here is an example of how we can use it:

//first add event listener
if (window.addEventListener)
    addEventListener('storage', storage_event, false);
else if (window.attachEvent) // for IE 8
    document.attachEvent('onstorage', storage_event);
// then we can handle the event
function storage_event(e) {
    if(!e) e = window.event;//for IE 8

    alert(e.url);//specify which page fire the event

    if (localStorage.length == 0)
    // clear occur
    else if (e.oldValue === null && e.key !== null && e.newValue !== null)
    // new key/value added
    else if (e.oldValue != null && e.key !== null && e.newValue !== null)
    // key/value updated
    else
    // specific key removed
}

localStorage Exceptions

loaclStorage throws two main exceptions one tells you that you exceeded your quota and the other tells you that you are trying to access other specific-domain storage.

  1. QUOTA_EXCEEDED_ERR
    Thrown if you exceeded your storage quota of 5 megabytes.

    try {
        localStorage.setItem(key, value);
    } catch (e) {
        if (e == QUOTA_EXCEEDED_ERR)
            alert('Quota exceeded!');
    }
    
  2. SECURITY_ERR
    Thrown if you tried to access a storage from different domain.

TO DO Application Using localStorage

Now, I’m going to demonstrate a simple TO DO application that stores it’s data using HTML5 localStorage and auto refresh other opened tabs or windows by handling storage event.

  1. Creating The html
    First we will create our html file which contains an input to the TO DO’s value and an ul tag that will hold the added value.

    <body>
        <form id="form1">
            <div id="content">
                <div>HTML5: TO DO Application Using Local Storage</div>
            <div>
                Add to List : <input type="text"/> <input type="submit" value="Add" />
            </div>
            <ul style="overflow:hidden; height:auto"></ul>
            <div>Double Click TO DO Item to Edit or <a href="javascript:void(0);" onclick="clearAll();">Clear all!</a></div>
            </div>
        </form>
    </body>
    
  2. Checking Browser Support
    First thing to do in our scripts is to check the ability of the browser to use localStorage.

    function supports_html5_storage() {
        try {
            return 'localStorage' in window && window['localStorage'] !== null;
        } catch (e) {
            return false;
        }
    }
    
  3. Loading The Storage
    After checking that browser supports localStorage, we need to check the current storage status and see if there are any stored items to load them.

    function setupTODOS() {
        var i = 0;
        var k;
        if( localStorage.length ) {
            for (i = localStorage.length - 1; i >= 0; i--) {
                k = localStorage.key(i);
                createNode(k, localStorage.getItem(k));
            }
        }
    }
    
  4. Binding Storage Event
    After loading previous values we need to bind our storage event, so we can handle all events fired when the storage changed and to update all opened browsers and tabs.

    if (window.addEventListener)
        addEventListener('storage', storage_event, false);
    else if (window.attachEvent)  // for IE 8
        document.attachEvent('onstorage', storage_event);
    
  5. Creating a Node
    We need to create a node and add its key/value to our local storage ( use a unique key for each entry by using Date.getTime() which return a number represents the time difference scenic 1970 till now).

    try {
        var seq = new Date().getTime();
        localStorage.setItem(seq, $("input[type=text]").val()); // adding element to localStorage
        createNode(seq, $("input[type=text]").val());
    } catch (e) {
        if (e == QUOTA_EXCEEDED_ERR)
            alert('Quota exceeded!');
    }
    
  6. Clearing The Storage
    We need also to support localStorage clean by calling localStorage.clean()

    function clearAll() {
        localStorage.clear();
        $("ul").html("");
        return false;
    }
    
  7. storage_event Function
    As you see in step 4, storage_event function is used to handle a localStorage event so we can update our UI in any open page, and here is the code:

    function storage_event(e) {
        //alert(e.url);
        if (localStorage.length == 0)
            $("ul").html("");
        else if (e.oldValue === null && e.key !== null && e.newValue !== null) {
            createNode(e.key, e.newValue);
        }
        else if (e.oldValue != null && e.key !== null && e.newValue !== null) {
            $("li[id='" + e.key + "']") .children("div:eq(0)").children("span").text(e.newValue);
            $("li[id='" + e.key + "']") .children("div:eq(1)").children("input").val(e.newValue);
        }
        else
            $("li[id='" + e.key + "']").remove();
    }
    
  8. createNode Function
    This function is the core function that handle any UI event and apply it to storage, also it is used to create the html elements used to display the value of the localStorage. It stores key as id for each li element, and add the value inside this li as a text, then adds an image to mark a TO DO is complete. It also enables dbclick event on the text to allow us to edit the value by creating a text field with two images one to accept the changes and the other to discard it.

    function createNode(k, value) {
        $("<img src='todolist.jpg' width='25px' height='25px' title='Done!' />")
        //append the value to new li
        .appendTo($("<div><span>" + value + "</span></div>")
        //adding key to li id
        .appendTo($("<li id='" + k + "'></li>").hide(). css('opacity', 0.0).prependTo("ul").animate({ opacity: 1.0}, 600, function () { }).slideDown(800))
       //this action hide the default view of  value to edit view
        .dblclick(function (e) {
            $(this).css('display', 'none');
            if ($(this).parent().children("div:eq(1)").length > 0) {
                $(this).parent().children("div:eq(0)").css("display", "none");
                $(this).parent().children("div:eq(1)").css("display", "block");
            }
            else {
                $("<img src='cancel.png'  width='25px' height='25px' title='Cancel' />")
                    .appendTo($("<img src='done.gif'  width='25px' height='25px' title='Edit' />")
                    .appendTo($("<div><input type=text value='" + $(this).children("span").text() + "'/></div>").appendTo($(this).parent()))
                    // this event used to accept the changes
                    .click(function (e) {
                        try {
                            var seq = $(this).parent().parent().attr("id");
                            localStorage.setItem(seq, $(this).parent().children("input[type=text]").val());
                            $(this).parent().parent().children("div:eq(0)").children("span").text($(this).parent().children("input[type=text]").val());
                            $(this).parent().parent().children("div:eq(0)").css("display", "block");
                            $(this).parent().parent().children("div:eq(1)").css("display", "none");
                        } catch (e) {
                            if (e == QUOTA_EXCEEDED_ERR) {
                                alert('Quota exceeded!');
                            }
                        }
                    }).parent())
                    // this event used to discard the changes
                    .click(function (e) {
                        $(this).parent().children("input[type=text]").val($(this).parent().parent().children("div:eq(0)").children("span").text());
                        $(this).parent().parent().children("div:eq(0)").css("display", "block");
                        $(this).parent().parent().children("div:eq(1)").css("display", "none");
                });
            }
        }))
        // finally this event used to mark an TO DO action as done so we can remove it from localStorage
        .click(function (e) {
            var seq = $(this).parent().parent().attr("id");
            localStorage.removeItem(seq);
            $(this).parent().parent().animate({
                opacity: 0.25,
                left: '+=50',
                height: 'toggle'
            }, 1000, function () { $(this).remove(); });
        });
    }
    

Summary

At the end of this article you can see that localStorage element is a great addition to HTML. It has many great features that we can sum up in the following:

  1. Local storage with 5MB for each domain.
  2. Wide range of support to browser platforms like IE 8+, FF 3.5+, chrome 4+.
  3. It uses easy key/value pairs structure to store data.
  4. It does not transmit values at each request like cookies.
  5. Never expires.

The demo also shows how strong it is by creating a simple TO DO application that works at the client,and which can be easily switched from an offline application to a chrome or FF extension.