Tuesday, July 24, 2012

Speeding things up with HTML5 Web Storage

Cache stuff in Javascript using Modernizr and the HTML5 API. And pronounce cache correctly.

On my brother Gavin's gallery website that I'm working on there's a sidebar which fetches a list of photo sets. Standard stuff. You choose a set and it loads a page with the querystring value of the set's id. It's an oldish WebForms site that I've been doing the jQuery and Web API business on.

I use a Web API project to fetch the list of sets from a Flickr account which Gav has set up, where each group of paintings ("available work", "small paintings in oil") is represented as a set of Flickr photos. It's a double hop: the website hits my Web API project, which hits the Flickr API. And as you can imagine, the list of sets doesn't change that often. So you can probably see this one coming from a looong way down the road. Anyway, in case you can't, here it is. Ready? I thought I'd cache the results.

But not in the .Net code. Because I'm asking for the data in the jQuery ajax() method, and pumping it to the screen on the .done() callback, I thought I'd test use the new (to me anyway) HTML5 Storage feature. It's seems like a good way to cache a small amount of data that is unlikely to change from page to page within the same session, and which is probably going to be pointless, or worse, fetching every single page load.


As a digression, it's weird the way here in Australia devs are wont to pronounce "cache" as "cay-che", not "cash". I grew up in Ireland where one of the stock phrases that you heard during the 80's was "arms cache", as in the British army found one in Northern Ireland, and said "cash", not "cayche". On podcasts and tech shows I only ever hear them say "cash". I spoke to Noel about this when I was back in Ireland a while ago. His wife's a Québécoise, a fluent French speaker, and la belle Catherine pointed out that the English word cache comes from the French word cacher: to hide away, store - and the root of that word is pronounced "cash". The French are losing enough words to internet English as it is - I think we should respect the words we owe to them by honouring the Gallic pronunciation appropriately where we can - just sayin'.

Framework à la carte

Anyway, I've become fascinated with Modernizr of late. I even like the pink fade on their website, the sort of styling that would normally be anathema to me. I can't work out what the logo means - the steps with a quarter circle - but to my great delight it animates when you mouse over it. And I love the way they make you choose what you need before downloading the library. It forces you to pay attention to what you're doing. These days, there are so many Javascript frameworks, plug-ins, and UI helpers, etc. that you can drown in different versions of slightly different libraries on your site. When stuff is free, you just take as much as you can get, I've found, so it's salutary for once to have to go through a list and pick what you need.

Since for the time being I'm only interested in that sessionStorage feature of HTML5, I can get a pretty pared-down version of Modernizr. My custom build amounts to a supermodel-skinny 9kb! This enforced customisation with Modernizr is because of the heterogeneity of HTML5. You are only likely to be interested in checking for the ability of your visitors' browsers to do a few particular things, so why download an exhaustive framework to cover every nook and cranny of the HTML5 features list?

It's code time. In GetSets() I check two things: first, does the user have a half-decent browser? In other words Internet Explorer 8+, Firefox, Opera, Chrome, or Safari? Those browsers all support HTML5 Web Storage. If they do, then see if they have already retrieved the data:
if (Modernizr.sessionstorage && sessionStorage['GLSets'])
If they don't have either, call out to the Web API url to get the sets from Flickr.
GetSets: function () {
        var self = this;

        // retrieve sets from local storage if already cached
        if (Modernizr.sessionstorage && sessionStorage['GLSets'])
            self.ShowSets(sessionStorage['GLSets']);
        else {
            var url = common.BaseUrl + "api/lavelleartgallery/sets?user=" + common.GLFlickrId;
            $.ajax({
                dataType: "jsonp",
                url: url
            })
            .done(function (data) {
                // cache sets in local storage
                if (Modernizr.sessionstorage)
                    sessionStorage['GLSets'] = JSON.stringify(data);

                self.ShowSets(sessionStorage['GLSets']);
            })
            .fail(function (jqXHR, textStatus, errorThrown) {
                // oops!;
            });
        }
    },

ShowSets: function (data) {
        var sets = JSON.parse(data);
        var listOfWorks = $('<ul></ul>');
        $.each(sets, function () {
            var set = {
                id: this.Id,
                name: this.Name,
                numberOfWorks: this.NumberOfPhotos
            };
            listOfWorks.append('<li><a href=\"Set.aspx?id=' + set.id + '\">' + set.name + ' (' + set.numberOfWorks + ')</a></li>');
        });
        $("#Sets").html(listOfWorks);
    }
And as soon as we get the data from Flickr (the .done() callback), stash it in the cache if we can. You'll notice that you have to serialize and deserialize the array of sets to set and get it from sessionStorage. That's because sessionStorage and localStorage only support strings.

Dive into HTML5 Web Storage

You might be thinking to yourself - I could have just saved this as a cookie if I wanted to avail of client side storage. In which case I refer you to the mysterious Mark Pilgrim's erudite "Dive into HTML5" book/website. If you read the chapter on Web Storage, you'll see a couple of ways in which the new Web Storage distinguishes itself from cookies: the data is not sent to and from the server on every request despite persisting beyond page refreshes.

This is all very handy, and browsing GavinLavelle.com, I can see a great improvement in the page load speed - the sets sidebar has no lag whatsoever, but - and it's a big one - the cache expires at the end of the session. I want to upgrade my HTML5 Web Storage to business class. I want to give it a bigger seat and more legroom. I want it to last as long as the data it's caching is current. That could end up being quite tricky. But there is no reliable way to expire localStorage - the counterpart to sessionStorage - as it just sits around for a long time apparently, so you have to come up with some expiration/refreshing strategy if you want to use it.