XHR Plugins with Dojo using handleAs

By on April 14, 2008 3:45 pm

Dojo’s Ajax system provides much more than basic text retrieval. As you might have already discovered from the pages in the Dojo book on using both text and JSON versions of Ajax requests, as well as the API page for dojo.xhrGet, the handleAs parameter lets us specify how we want the returned data to be parsed.

From the API page, on the handleAs parameter:

Acceptable values are: text (default), json, json-comment-optional, json-comment-filtered, javascript, xml

What you may not know is that the handleAs parameter is merely a way of specifying what plugin to use. Knowing where these plugins are, how they work, and how they can be adapted to suit your project will allow you to make repetitive tasks easy and less error-prone.

dojo._contentHandlers holds a property bundle of plugins. For example, dojo._contentHandlers.text is used when handleAs is told to use the text plugin, and dojo._contentHandlers.json is used when handleAs is told to use the json plugin.

Not only are we able to create new plugins, but overriding the built-ins is an easy way to change the way all of our current Ajax transactions work.

Now that we know all of this, we can describe three situations in web development that will benefit from control over Ajax result processing: preprocessing requests, handling tasks without the need for callbacks, or changing the lifecycle of your call.

We’ll turn these three benefits into full-blown examples.

Example 1: Updated Objects

In any thick client, one of the worries is data going stale. One of the solutions to this is to frequently poll the server to pull down the updates since a certain time. In a very active application, this adds yet another server request. What we can do instead is to have every server request also return the updated objects. As a failsafe, a no-op transaction could be called when there’s been no activity for a certain amount of time.

All of this can be done without changing any of your code. You’re likely using the JSON handler, so we’ll just go ahead and override this:

dojo._contentHandlers.json = (function(old){
  return function(xhr){
    var json = old(xhr);
    if(json.updated){
      processUpdatedObjects(json.updated);
      delete json.updated;
    }
    return json;
  }
})(dojo._contentHandlers.json);

You’ll notice the function wrapper which returns a function. Quite simply, it’s one way of making sure that we save a reference to the old plugin. Our returned function is what will actually be used as the JSON handler. Assuming that the server stores the list of updated objects in the updated key, we call some code that processes this object list, removes the key from the data, and then returns the results as normal.

With this simple technique, you can add all sorts of extra data to the content returned by your server without having to even worry about dealing with it in any of your callbacks.

Example 2: Updating Node Content

In my article on Functional Ajax with Dojo, I discussed the topic of updating a node with the result of an Ajax call. One of the most important nuggets of wisdom from that article is the fact that the arguments passed to your xhr call are added to the xhr object which is passed to all handleAs plugins and all callbacks. This means that we can write an extremely flexible plugin that can determine what to do based on passed arguments.

I’ll propose two different plugins that accomplish the same basic task. First, we’ll write one in which the server determines what node IDs to update, and our second plugin will be one in which the server merely provides HTML that fills in an ID that the client has passed.

Both of the calls appear as follows:

dojo.xhrGet({
  url: "node-updates.php",
  handleAs: "node-update-server"
});

dojo.xhrGet({
  url: "node-content.php?node=sidebar",
  node: "sidebar",
  handleAs: "node-update"
});

And write their corresponding plugins:

dojo.mixin(dojo._contentHandlers, {
  "node-update-server": function(xhr){
    var json = dojo._contentHandlers.json(xhr);
    dojo.forEach(json.updates, function(update){
      var node = dojo.byId(update.id);
      if(node){
        node.innerHTML = update.html;
      }
    });
  },
  "node-update": function(xhr){
    var node = dojo.byId(xhr.args.node);
    if(node){
      node.innerHTML = dojo._contentHandlers.text(xhr);
    }
  }
});

Our first plugin assumes a list of updates will be returned by the server with references to node IDs and their content. In the second plugin, we assume that the node is passed in as an argument. In either case, we use innerHTML to update the content.

Example 3: Changing the Lifecycle

Dojo currently provides a robust callback mechanism: The load and error functions are called for the respective success or failure of your Ajax call, and the handle function is called no matter what.

Let’s say that you decide that this isn’t how you want to do things. Instead, you want a full lifecycle. You decide that you want a preprocessing function, a handler function, and a post-processing function.

Important to note is that you won’t be able to use any of the named functions above (load, error, or handle) since we’re not overriding the normal lifecycle of the xhr system. If we were to call any of these functions in our plugin, they’d end up getting called twice. If you absolutely need to use this style, just make sure that your handleAs function returns no data, and then add a check in the functions to see if data is present or not.

dojo.xhrGet({
  url: "dosomething.php",
  handleAs: "lifecycle",
  preprocess: mylibrary.preprocess,
  process: function(data){
    // Call-specific processing
  },
  postprocess: mylibrary.postprocess
});

We’re introducing the various process plugins in our lifecycle. Hopefully, I’ve shown in this example the possible benefits of this system. While preprocessing is available normally by changing the handleAs type, we’re introducing post-processing here, which wouldn’t normally be available without explicitly calling that function in your callback. Also, you can use preset functions for various lifecycle tasks that are unique to each call.

dojo._contentHandlers.lifecycle = function(xhr){
  var args = xhr.args;
  var json = dojo._contentHandlers.json(xhr);
  if(args.preprocess){
    args.preprocess(json);
  }
  if(args.process){
    args.process(json);
  }
  if(args.postprocess){
    args.postprocess(json);
  }
}

Of course, you can take this as far as you want. Since you have available to you the full list of arguments, you have the same amount of power and flexibility as you would if you wrote the function over from scratch.

Conclusion

Dojo makes it incredibly easy to change the way that your Ajax calls work. You can change the format of JSON your server returns without having to change any of your callbacks, you can change the handleAs type for a single function to change the data given to your callback, you can get rid of callbacks altogether and use the arguments to your xhr call determine what should be done with your results.

Happy coding!

Comments

  • If we are going to recommend something like this (which I think is nice, easy, and smart), I think that _contentHandlers should be made into a public method instead of a _private method. If not there is no point in having a public api and/or _private functions. Of course in some cases people knowingly take advantage of a private method with the knowledge that it might fail to work with a future release. That is different than recommending that developers do this in their code.

  • Tom C

    I am having a problem with this code. It appears that the arguments passed to the xhr call are NOT added to the xhr object passed to the handleAs plugin. I created the simplest content handler, and when it is called, the xhr object (of type XMLHttpRequest) does not have any of the passed arguments. Here is the code:

    function loadData() {

    dojo.xhrGet({
    url: “/assignment_data/testing”,
    handleAs: “customized”,
    seeyou: “value”,
    seeme: function(data) {
    console.debug(“seeme called!”);
    }
    });
    }

    dojo._contentHandlers.customized = function(xhr){
    var args = xhr.args;
    //var json = dojo._contentHandlers.json(xhr);
    console.debug(“xhr = “, xhr);
    console.debug(“args = “, args);
    console.debug( args.seeyou );
    }

    Do you see a problem here? Am I doing it wrong?

  • JP

    xhr.args.node is an issue in node-update. The XHR object doesn’t have args as part of it, only things related to the returned data in the handleas part. So the two examples that rely on that won’t work. The node-update-server does work however and is a really nice example of how to implement a system were the server can update multiple parts of the UI.