A Better Cookie: HTML5 and Web Storage

One of the more interesting and useful new features to come out of the HTML5 spec is Web Storage, which provides us with up to 5MB of persistent storage of data for our web applications.

In this article, we will be enhancing an example begun in an earlier blog, A Drag and Drop Planning Board with HTML5. This example built a simple agile planning board with two notecards that you could drag and drop onto different sections of the board. In this article, we’ll review how to actually store the last location of each of the cards, so when you return to the site, the cards are where you left them.

You can find the completed example on github. However, I encourage you to start with the initial page, and try to build in the saving of the notecards with me throughout this article. To do so, you can find the pre-Web Storage version of the code here, and the demo page of that code here.

There are two kinds of storage defined in Web Storage: Session Storage and Local Storage.

Session Storage solves a problem that’s plagued us with cookies: accidentally purchasing two copies of the same item. Assume that you’re shopping for plane tickets online, and you have multiple windows open to try and find the best deal. Even though you have multiple windows open, if the site is using cookies to track your orders, the cookies are all shared across the multiple windows. This could result in accidentally purchasing the same ticket twice.

SessionStorage solves this problem for us. With Session Storage, we have a separate data store for each window or tab that’s open. The data is not persisted beyond a session, so if you close the window or tab, the data disappears. But, while you’re shopping, the data in one window will be kept completely separate from that in another window if the site leverages the session storage data store.

But what about values you do want to save to disk? In our planning board, we want to be able to remember the last location of the notecards we dragged and dropped. To store their last location, we’ll need to take advantage of the second kind of Web Storage, called Local Storage.

Local Storage provides a persistent data store of up to 5MB. One important thing to note about Local Storage is that each browser will have its own, individual 5MB store, and data is not shared between browsers.

Data saved using Local Storage are stored as key/value pairs. A key/value pair is a common way to store data by keeping track of two pieces of information: the key, and the value. The key describes what the information is, and the value is the actual piece of data you would like to associate with the key.

Local Storage provides us with an object, named localStorage, that allows us to get and set our key/value pairs.  Both values must be saved as strings—even if we will convert them to other data types later.  To set a key/value pair, we call: localStorage[key] = “value”.  To get a value from a given key, we call: var myValue = localStorage[key].

We’ll leverage this localStorage object in order to store the locations of the two notecards. First, we’ll define a function called saveLocations that will save the locations of the notecard and the parent we dropped it into:

function saveLocations(parent, notecard){
    // save the locations of notecards after each drop event
    // grab the notecard via jQuery using ids
    var parentId = parent.getAttribute('id');
    var key = notecard + "parent";
    // store the notecard's parent id in localStorage
    localStorage[key] = parentId;
}

We get the id of the parent by calling parent.getAttribute(‘id’). Next, we create a key that is the concatenation of the notecard’s id (which will be passed to the function as the second argument) and the word “parent”. So, for example, the key for the parent of the first notecard will be “item1parent”.  As a final step, we go ahead and store the parentId value in the localStorage collection, under the value stored in the key variable.

Let’s call saveLocations after an item is dropped, so we can be sure that every time an item is moved via drag and drop, we save the new location to localStorage. We’ll add it to the end of the anonymous function that’s bound to the drop event in the init() method. We wrote this code when we first created this example, in A Drag and Drop Planning Board with HTML5. Here, we’re simply adding the line at the end:

// bind the drop event on the board sections
      $('#todo, #inprogress, #done').bind('drop', function(event){
          var notecardId = 
event.originalEvent.dataTransfer.getData("text/plain");

event.target.appendChild(document.getElementById(notecardId));
          event.preventDefault();
          saveLocations(event.target, notecardId); 
      });

We pass the event.target, which is one of the bulletin boards, and the id of the notecard that was dragged and dropped.

So now we are saving the new locations, but what about loading these saved values once we reload the page?

To re-load the values saved to localStorage, we’ll create another new function called loadLocations. First, we retrieve the parent of both notecards by accessing the values stored in the keys “item1parent” and “item2parent”:

function loadLocations(){
    // find the parent for item1 and item2
    var item1parent = localStorage['item1parent'];
    var item2parent = localStorage['item2parent'];
}

Next, we will test to ensure there is a value stored in each new variable. If there is, we must remember that the value stored is a string, not an object. In order to get the actual HTML element of the parent, we must grab the element by its id via document.getElementById(). We do the same thing for the notecards. Finally, we will go ahead and move the notecards from their default location in the first section of the bulletin board to their actual last location, by appending the notecard element into the parent element:

function loadLocations(){
    // find the parent for item1 and item2
    var item1parent = localStorage['item1parent'];
    var item2parent = localStorage['item2parent'];

    if (item1parent) { 
        var parent1 = document.getElementById(item1parent);
        var notecard1 = document.getElementById('item1');
        parent1.appendChild(notecard1);
    }
}

We repeat the same process for the second item’s parent:

function loadLocations(){
    // find the parent for item1 and item2
    var item1parent = localStorage['item1parent'];
    var item2parent = localStorage['item2parent'];

    if (item1parent) {
        var parent1 = document.getElementById(item1parent);
        var notecard1 = document.getElementById('item1');
        parent1.appendChild(notecard1);
    }

    if (item2parent) {
        var parent2 = document.getElementById(item2parent);
        var notecard2 = document.getElementById('item2');
        parent2.appendChild(notecard2);
    }
}

As a final step, we’ll call loadLocations() from the init() method we created in the initial example, in A Drag and Drop Planning Board with HTML5:

function init(){
  loadLocations();
  .
  .
  .
}

Now, when we move the cards around the bulletin board, even if we close and re-open the site, the cards will be where we left them, all thanks to Web Storage!

There’s plenty more HTML5 goodness coming very, very soon in HTML5 and CSS3 for the Real World, co-authored by Estelle Weyl, Louis Lazaris and yours truly.

In the meantime, tell us your thoughts about using Drag and Drop with Local Storage.

Win an Annual Membership to Learnable,

SitePoint's Learning Platform

  • Stormrider

    Don’t know if the problem still exists because I can’t see a hosted demo of the new one, but I found a problem with the demo linked to at the top of the article: You can drag notes inside each other. If you then drag the outer note into the inner one, it doesn’t like that….

    • Alexis Goldstein

      Hey Stormrider,

      I did notice that you could drag cards into each other which I thought was quite cool, but hadn’t notice that the functionality then doesn’t work on the new grouped cards. I’ll have a look…

  • bluesix

    “Even though you have multiple windows open, if the site is using cookies to track your orders, the cookies are all shared across the multiple windows. This could result in accidentally purchasing the same ticket twice.”

    That’s actually a GOOD thing, since you can update a user’s “cart” in the second window when something happens in first one.

    I think what you might mean is that you can be logged into a site under *2 different usernames*, using the same browser. That’s the main benefit I see. Being able to have 2+ twitter accounts open in the same browser will be great.

    • Alexis Goldstein

      Hey bluesix,

      What I was trying to convey is probably expressed best from the source itself, the W3C spec, where I drew inspiration for the example. The W3C describes the case of orders or tickets “leaking” from one page to another: http://dev.w3.org/html5/webstorage/#introduction

      I agree that 2+ twitter accounts in the same browser would be GREAT! All my secret twitter aliases, you know :) …

  • Kirk

    Good stuff, Alexis, thanks for providing this! I also noticed an issue. If you start to drag a card, but then drop it back onto itself, an http request is generated with a url of the form “http://item2/”.

    • Alexis Goldstein

      Hey Kirk,

      Good catch, I’ll try and push a fix for that to github soon.

  • Martin English

    Hi Alexis,
    It’s not apparent from the article, or the code segments presented with the article, but there are some extra changes to the init() function (when compared to the original article). I’ve experimented with undoing these speciifc changes and there doesn’t appear to be much change in behaviour. Could you explain why these changes were made ?

    The other issue I had was that I kept testing it in FireFox 4 !!!. The drag-and-drop works fine, but not the local storage. Both worked fine in Chrome 11. In future articles, could you please specify (for newbs like me) what browsers support the functionality you are demonstrating ?

    Apart from those minor quibbles, thanks for the excellent work !!

    • Alexis Goldstein

      Hey Martin,

      Great questions! I should have made a note of the browser support. This is included in the book!

      Web Storage is supported in:
      Safari 4+
      Chrome 5+
      Firefox 3.6+
      Internet Explorer 8.0
      Opera 10.5+
      iOS (Mobile Safari) 3.2+
      Android 2.1+

      The problem you’re seeing with Firefox is one that *plagued* me! The reason I’m guessing that it’s happening to you, is that you’re viewing a file from your computer. FF’s implementation of localStorage doesn’t work if you’re viewing a file on your computer. Mozilla is aware of the bug, which you can find here. You also may want to check and see if local storage is turned on for you in Firefox, by typing “about:config” into the URL bar, and the looking for the value of dom.storage.enabled. It should be set to true.

      I’m not sure what changes you mean in the init() function apart from the additions of saveLocations(); and saveLocations(); Tell me more about the differences you noticed.