XML.com: XML From the Inside Out
oreilly.comSafari Bookshelf.Conferences.

advertisement

An AJAX Caching Strategy
by Bruce Perry | Pages: 1, 2, 3

Prototype's Ajax Object

The AJAX-related code using Prototype's Ajax.Request object is only about ten lines long. The code connects with a server, gets a new harvested energy price from the U.S. government, then displays the price without changing any other part of the web page. The code is, however, somewhat confusing to look at. Here's an explanation.

You are probably used to seeing the XMLHttpRequest object being programmed directly. Prototype, however, instantiates this object for you, including dealing with the different browser types the users will show up with. Just creating the new AJAX object, as in new Ajax.Request, sets the HTTP request in motion. The prototype.js file includes the definition of this object. Since our web page imported that JavaScript file, we can create new instances of this object without jumping through any other hoops. Our code doesn't have to deal with the various browser differences involving XHR.

The first parameter to Ajax.Request is the URL of the server component it is connecting with. For example, http://www.eeviewpoint.com/energy.jsp. The second parameter specifies that this is a GET HTTP request type. The third parameter provides a querystring that will be passed to the server component. The entire URL, therefore, might look like http://www.eeviewpoint.com/energy.jsp?priceTyp=oil for example.

Progress Indicator

A couple of other parameters let the code developer decide what the application should do at different stages of the request processing. For example, onLoaded represents a stage of the request sequence when the XMLHttpRequest.send() method has been called, and some HTTP response headers are available, as well as the HTTP response status. At this stage, our application displays the text "waiting..." where the new price will soon appear. onComplete represents the finished stage of the energy-price request. The function that is associated with onComplete, therefore, determines what happens with the request's return value. The code automatically provides the function with a request parameter that represents the instance of XHR used for the HTTP request.

onComplete:function(request){...

Therefore, in this case, the code gets the return value with the syntax request.responseText. If everything goes well, this property will be the new price, such as 66.07.

The Ajax.Request object also allows the code to take action based on certain HTTP response status values. This is accomplished by checking the status property of XHR (request.status). If the status is not 200 (it could be 404 meaning "Not Found" for example), a value signifying that the request was successful, then the application displays "unavailable" in the space where the price should be.

We could change this behavior so that an unsuccessful request just leaves the current price display the same.

new Ajax.Request(go_url, {method: "get",
    parameters: "priceTyp="+energyType,
    onLoading:function(){ $(elementID).innerHTML=
       waiting...";},
    onComplete:function(request){
        if(request.status == 200) {
           $(elementID).innerHTML=
           request.responseText;}
}});

The CacheDecider Object

The final piece of the client-side puzzle is the CacheDecider object. Figure 1-2 is a UML diagram depicting this object. I wrote the object in JavaScript using the Prototype library.

UML class diagram for CacheDecider
Figure 1-2. A Class diagram depicting CacheDecider

Here is the code for the object, which appears in the eevapp.js file.

var CacheDecider=Class.create();
CacheDecider.prototype = {
    initialize: function(rng){
        //How long do we want to
        //keep the associated data, in seconds?
        this.secondsToCache=rng;
        this.lastFetched=new Date();

    },
    keepData: function(){
        var now = new Date();
        //Get the difference in seconds between the current time
        //and the lastFetched property value
        var secondsDif = parseInt((now.getTime() - 
        this.lastFetched.getTime()) / 1000);
        //If the prior computed value is less than
        //the specified number of seconds to keep
        //a value cached, then return true
        if (secondsDif < this.secondsToCache)  { return true;}
        //the data in the cache will be refreshed or re-fetched,
        //therefore reset the lastFetched value to the current time
        this.lastFetched=new Date();
        return false;
    }
}

The prototype.js file includes an object definition for Class. Calling Class.create() returns an object with an initialize() method. Like a constructor method in other languages, JavaScript will call this method every time the code creates a new instance of an associated object.

Our initialize() method for CacheDecider initializes two properties: secondsToCache, which represents the number of seconds to cache a value before another can be fetched; and lastFetched, a JavaScript Date object representing the last time the cached value, whatever it is, was refreshed.

CacheDecider does not store a reference to a cached value; other objects use a CacheDecider to store and check time limits or ranges.

If this is not clear, check the explanation beneath the "Check the CacheDecider Object First" subheading.

CacheDecider also has a keepData() method. This method determines whether the number of seconds that has elapsed since the lastFetched date exceeds the number of seconds that the cached value is supposed to be kept.

In our application, we hold on to an energy-price value for 24 hours, until it can be refreshed with an HTTP request.

cacher = new CacheDecider(60*60*24);

If this number of seconds has not been exceeded, then keepData() returns true. Otherwise, the lastFetched property is reset to the current time (since the cached value can now be refreshed via an HTTP request), and the method returns false.

The user can update the data whenever they want if they reload the entire web page. This action will initialize anew all of the objects that we have been discussing.

The Server Component

This application uses a kind of "cross-domain proxy" design pattern, as discussed at this informative website: http://ajaxpatterns.org/Cross-Domain_Proxy. Even though my application is accessing information from the U.S. EIA web pages, the scraping is initiated by a component that derives from the same domain (eeviewpoint.com) as our application web page.

The server component can be written in your language of choice for scraping web-page information: PHP, Ruby, Python, ASP.NET, Perl, Java. I chose Java, being partial to servlet/JSP programming and the Tomcat container. The Ajax.Request object connects with a JSP, which uses Java utility classes to harvest the energy information from U.S. EIA sites. See the "Resources" box to learn where to download the Java classes I used.



1 to 2 of 2
  1. browser cache
    2006-05-04 15:47:44 jleech
  2. Nice article - but worthwhile
    2006-05-04 06:09:52 dunxd
1 to 2 of 2