Warning: count(): Parameter must be an array or an object that implements Countable in /homepages/28/d316380528/htdocs/webSites/andresGallo/wp-includes/post-template.php on line 293

In parts one and two of “Ajaxifying the web, the easy way” I spoke about moving click actions into the browser history. This allows for a ton of flexibility as well as code reduction, in modern javascript applications. In this the third and last part of our tutorial, I am going to cover caching. This is my opinion the icing on the cake, and what makes the script we are building really awesome. The filtering mechanism found at marvel’s new website sections for example makes heavy use of the caching ideas I will explain in this tutorial. Caching is beneficial both in terms of bandwidth as well as user experience.

As we add more features our js gets more complex. Programming design patterns now become more important.

Before we can proceed to the implementation of our caching machanism lets first cover the deferred events design pattern which is handy for a task such as this. Knowing how to write this pattern from scratch is not required, but here is a link to more information for those of you who may be curious about it.

So the idea behind deferring events is that we can have asynchronous actions such as ajax, timeouts and yet force them to run in a predictable order. As you saw before we had to include the code that runs after the ajax request inside the function callbacks named success and error. Now that our logic is going to become slightly more complex, we will want more control than just those two function callbacks, and this is why this design pattern is important to understand. To start lets convert our previous ajax call to use the deferred pattern using jQuery.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
fetchData(hashContent){
   //I moved  the variables for ajax call here
   var scriptName = hashContent.mainValue;//This is passed to the url request in our ajax call
   var myParameters = hashContent.parameters;//This is passed as the parameters in our ajax call

   //Here is the same ajax call we had previously, but you can see its written differently.
   //Instead of success and error, we have two functions inside the .then method
   return $.ajax({
      url : '/location/of/my/ajax/scripts/'+scriptName+'.php',
      dataType : 'json',//Whatever data type the calls are making
      data : myParameters
   }).then(function(response){//Same as success function
      //WE CAN NOW CACHE THE RESPONSE BEFORE RETURNING IT
      return response;
   },function(err){//Same as error function
      //We need to handle what happens on every error
      return err;
   });
}

doSomethingAccordingToHashValue(hashContent){
     console.log(hashContent);//View the object returned by our tool

     //Ajax call moved to a fetchData function, it now uses $.when, and $.then syntax in jquery for doing deferreds with ease
     fetchData(hashContent);
     
     //Here is the old ajax call for reference.
     /*
     $.ajax({
       url : '/location/of/my/ajax/scripts/'+scriptName+'.php',
       dataType : 'json',
       data : myParameters,
       success : function(response){
           //We could pass this to a  templating engine, or a template generating script.
           //What you do with the data is up to you
       },
       error : function(response){
           //We need to handle what happens on every error
       }
     });
     */

}

window.addEventListener('hashchange',function(){
  //Triggered when hash in url is changed changed.
  //You may want to use a library like jquery to listen for the hash change to support older versions of IE

  var hashContents = getHashContents();
  doSomethingAccordingToHashValue(hashContents);
});

As can be seen above not much was changed from our last piece of code which we wrote in Part II of the tutorial. The only change was some small refactoring, and the application of the deferred pattern to our ajax call using jQuery’s then method. This allows us to do do things with our data before we receive it presenting a perfect place for us to cache the response. This is extremely useful as it allows us to write a function in such a way that it either gets and caches the data, or just gets us the cached data. There are applications for this technique far beyond the scope of this tutorial.

Now that we understand deferring using jQuery’s then method, lets actually take this to the next level and add caching.

We will be adding a single level of caching for our successful ajax responses, though once you are comfortable with this, the same idea may be used to cache errors based on their error code, while selective keeping some uncached (intermittent connection difficulties for example are best not cached).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
_cache = {};//We will cache things here

fetchData(hashContent){
   var scriptName = hashContent.mainValue;//This is passed to the url request in our ajax call
   var myParameters = hashContent.parameters;//This is passed as the parameters in our ajax call
   
   //Lets make a cacheKey.
   //You can think of these keys as if they are the names for the drawers in which to store our request's responses.
   //We will use the url of the request as our key
   var requestUrl = '/location/of/my/ajax/scripts/'+scriptName+'.php',
       _cacheKey = decodeURIComponent(requestUrl);//Make sure to format properly for object key
   if(_cacheKey in _cache && !$.isEmptyObject(_cache[_cacheKey]) ){//Lets check it is in cache and that it has data
      return _cache[requestUrl];
   else {//Else get and cache this data and then return this data to us, so we can do something.
     return $.ajax({
         url : requestUrl,
         dataType : 'json',
         data : myParameters
       }).then(function(response){//Same as success function
       _cache[_cacheKey] = response;//Here we are caching the response in our cache variable
       return response;
         },function(err){//Same as error function
            //We need to handle what happens on every error
      return err;
     });
   }
}

As you can see above we added a new variable called _cache. This variable is an object and is where we are storing every response. For our code to be able to know where to find its data within the cache, we then used the requestUrl as our cacheKey. With this setup we call fetchData to make ajax requests while having it optimize all these requests with proper caching on the client side. Using localStorage for our _cache we can even take this to the next level giving us a persistent cache (More on this near the end of the article)

Another handy method is the $.when method. It allows us to wait for multiple asynchronous functions in one shot, before applying our magic. Lets apply this

1
2
3
4
5
6
7
8
9
10
11
12
13
doSomethingAccordingToHashValue(hashContent){
     console.log(hashContent);//View the object returned by our tool

     //Using &.when to give us extra flexibility
     $.when(fetchData(hashContent)).then(function(response){
        //We could pass response to a  templating engine, or a template generating script.
        //We could do anything with this response
        //All our ajax responses will get cached, making them faster when called a second time
        //A common use is to add content to a page, or to create "Mobile" one page websites where the page contents animate.
     },function(err){
        //Handle errors
     });
}

Bringing it all together into one awesome script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
_cache = {};//We will cache things here

fetchData(hashContent){
   var scriptName = hashContent.mainValue;//This is passed to the url request in our ajax call
   var myParameters = hashContent.parameters;//This is passed as the parameters in our ajax call
   
   //Lets make a cacheKey.
   //You may think of these keys as the drawers where we store/cache each of the requests' responses.
   //We will use the url of the request as our key
   var requestUrl = '/location/of/my/ajax/scripts/'+scriptName+'.php',
       _cacheKey = decodeURIComponent(requestUrl);//Make sure to format properly for object key
   if(_cacheKey in _cache && !$.isEmptyObject(_cache[_cacheKey]) ){//Lets check it is in cache and that it has data
      return _cache[requestUrl];
   else {//Else get and cache this data and then return this data to us, so we can do something.
     return $.ajax({
         url : requestUrl,
         dataType : 'json',
         data : myParameters
       }).then(function(response){//Same as success function
       _cache[_cacheKey] = response;//Here we are caching the response in our cache variable
       return response;
         },function(err){//Same as error function
            //We need to handle what happens on every error
      return err;
     });
   }
}

doSomethingAccordingToHashValue(hashContent){
     console.log(hashContent);//View the object returned by our tool

     //Using &.when to give us extra flexibility
     $.when(fetchData(hashContent)).then(function(response){
        //We could pass response to a  templating engine, or a template generating script.
        //We could do anything with this response
        //All our ajax responses will get cached, making them faster when called a second time
        //A common use is to add content to a page, or to create "Mobile" one page websites where the page contents animate.
     },function(err){
        //Handle errors
     });
}

window.addEventListener('hashchange',function(){
  //Triggered when hash in url is changed changed.
  //You may want to use a library like jquery to listen for the hash change to support older versions of IE

  var hashContents = getHashContents();
  doSomethingAccordingToHashValue(hashContents);
});

Putting together all the pieces we now have three different components working together, yet decoupled enough to use separately. We have our hashchange event to listen to whenever there is a hashchange events. We have our doSomethingAccordingToHashValue function which can serve as our router. At the moment the only route is to our third component which we named fetchData. This component will fetch data from the server or from cache if it has already fetched it from the server.

So we covered caching in an object, but what if we prefer localStorage.

We can certainly use localStorage for the same purpose. Instead of storing data in the _cache variable we can store it directly in localStorage. With that said, it is important to know the limitations of localStorage. One of this limitations is the fact that it stores data persistently which although wonderful makes it unsuitable for some scenarios. The other limitation is it can only store simple data, so it will not work for objects, or arrays, though I have a solution to that. I wrote a script to allow easy storage and retrieval for all sorts of data in localStorage. The script is called truStorage and can be found at https://github.com/andresgallo/truStorage. I have two versions of the script, with a tutorial on using them at github. One version is basic, and the other one allows using javascript modifiers as well.

We have now built a trio of components to make certain ajax tasks easier to maintain, and reuse. With these components in place, logic to add cache expiration, and other bells and whistles can be added to create a really badass routing framework as well as an endless amount of other applications. I hope this tutorial has been helpful, and look forward to seeing what you all build using these ideas. In the future I will write about my favorite design patterns which will help make complex scripts even simpler maintain, and reuse.

Share this:

Share this page via Email Share this page via Stumble Upon Share this page via Digg this Share this page via Facebook Share this page via Twitter
If you enjoyed this post, make sure you subscribe to my RSS feed!

Comments RSS and TrackBack Identifier URI ?
Do you want to comment?

One response


Comment now!