– or: How to persist data in the 21st century.
The common way to persist data on the client side – application state, offline data, whatever – still is to use cookies. But times have changed, and so have browsers, and there are better ways to do it today.
But why are cookies that bad? Well, here are the top three reasons:
- Of all client side storage mechanisms, cookies have the worst limitation in size (4k if you want to stay IE-safe)
- Cookies are sent to the server on every requests that matches the cookie domain – inlcuding XHR calls (aka. How to slow down your AJAX app)
- Cookies perform bad, can be easily disabled, and, oh well, they are sooo 1995…
What else to use? There are several options, let’s start with the best:
localStorage
This is the storage mechanism proposed by the HTML5 draft. It offers you a fast, reliable key-value store with a whopping 5M (!!) of storage space. And here’s the kicker: It’s implemented in all current major browser releases. That is, FF, Safari, Opera, IE and Chrome/Chromium. Yes, you name it, all of them, the full list. To read more about localStorage, check the HTML5 draft. But here’s all you need to know in short:
Storage Methods
To test if the user agent supports localStorage, just test for the existence of window.localStorage
. Working with the storage object is trivial:
Setting a value:
localStorage.setItem('key','value');
Though the draft says the user agent should write a “structured clone” of the value to store, don’t think that you can store objects – you can only store strings. To store objects, you need to convert them to JSON before.
To retrieve a value, there are two options. First, you can get a value by it’s key:
var value = localStorage.getItem('key');
The user agent will return the value for the key, or, if it doesn’t find a matching record, null
. Also, you can retrieve a key by it’s index:
var value = localStorage.key(n);
The storage object has a length
property, and n is a number between zero and length-1. You do not get the index of a value when inserting it, but you can use the key()
method when iterating over all items in the store using the length
attribute.
There is another method to retrieve a value, but as this not documented (at least I didn’t find anything about it), I do not recommend it:
var value = localStorage.key;
Deleting an item:
localStorage.removeItem('key');
Deleting the whole storage:
localStorage.clear();
This clears only the storage objects of the same storage area. That is, storage objects of the current domain.
So far, everything is pretty trivial, but let’s look at
Storage Exceptions
When accessing the storage object, a user agent may raise an SECURITY_ERR
exception – but I have never managed to trigger one. When setting an item, a user agent may raise an QUOTA_EXCEEDED_ERR
exception. This is not only for the case when storage space is at it’s limit, but may be raised for other reasons as well. Again, I have never managed to trigger it, but it may be wise to wrap a setItem
call in a try/catch block.
Storage Event
Now things get tricky, and the different browsers have different (if any) implementations. Ok, first, the theory: methods that modify the storage object (setItem()
,removeItem()
,clear()
) do not report if – or when – the changes are in effect. To get notified about this, you need to connect to the storage event:
window.addEventListener("storage", function(evt){ // a change to the storage object has been attempted. }, false);
The event should have the following attributes:
- key
- oldValue
- newValue
- url (the address of document object whose storage object was modified)
- storageArea (the affected storage object)
With this information, you can easily determine to what change the storage event belongs. But, alas, that event is not fully implemented everywhere. Here’s what I found out:
FF 3.6.3 lets you connect to the event, and fires it according to the draft. But it does not contain *any* of the fields mentioned above.
Safari 4.0.5 lets you connect to the event, and fires it according to the draft. It’s event contains all attributes except url.
on Chrome 5 & Chromium I couldn’t connect to the event at all or it wouldn’t let my execute code in my callback function – I have no idea if it’s even fired.
Latest WebKit nightly wouldn’t report anything to me, like Chrome.
Opera 10 lets you connect to the event, fires it accordingly, and the event contains all attributes (woot!)
IE 8 didn’t report anything back to me, too.
I used this simple test file to check for the event. Feel free to test other browsers and report what you found!
Other options
If the browser is too old to have localStorage, there are still other options to persist data: Firefox 2 had globalStorage, which behaves similar to localStorage. IE has userData beahvior since version 5.5. Safari has it’s built-in Sqlite database, but I don’t know since when. Plus, there’s also plugin-based storage via Flash or Gears. Only if they all fail, you should get back to cookies.
Of course, it would be a lot of work to write wrappers for all these storage mechanisms – but, hey, you don’t have to, others have already done it. There are several toolkits that do the work for you, and I will present three of them.
Dojo
The dojo toolkit has dojox.storage. It currently offers support for Flash, Gears and globalStorage. But with the files you can grab at my previous post on storage, you gain access to localStorage, userData behavior and cookies. Working with dojox.storage is pretty simple, and you don’t have to worry about anything: dojo’s storage manager will pick the best available provider. Here’s a simple example:
dojo.require("dojox.storage"); dojox.storage.put('key','value'); var value = dojox.storage.get('key');
That’s it. Simple as that. You do have more options, though; you can also specify a namespace for your keys – so you can simulate working with different “tables” in one store. You can also specify a callback function for the put()
method, to get notified when the changes are in effect and if the change was successful.
Lawnchair
Lawnchair has been created by Brian Leroux from Nitobi. Working with it is dead simple, too. It supports localStorage, userData behavior, Gears, Webkit’s Sqlite and Cookies. It works more like a document store (you can save an object w/o specifying a key, for example), but you can also use it like a straight key/value store. In opposition to dojo’s storage system, Lawnchair works completely asnc, so you have to pass a callback function to a get() method to retrieve a value (being async is not a drawback, and it is neccessary to work with WebKit’s Sqlite). You can find some docs here and get it over at Github.
Persist.js
Persist.js has support for localStorage, globalStorage, Gears, Flash, userData behavior and Cookies. It also works completely async. You can get it at Github.
According to Anne van Kesteren, there’s nothing wrong with that technique and it’s okay to use it. No need to advise against it :) http://krijnhoetmer.nl/irc-logs/whatwg/20100913#l-181
@Mathias Bynens
Thanks for the pointer :) True, read and write actions via dot notation work fine in all browsers supporting localStorage, and advising against it might be a little too strong. Still I can’t find the IDL that describes this, and, what’s more important, using dot notation might leave the impression that localStorage is just an object like any other – but it isn’t.
So just for the sake of clarity and code readability, I’d still go for the “API” methods :)