Saq made a couple of comments on my ChainCollector article about
how to queue up functions to respond to Ajax calls, and whether I could write
something up to shed a bit of light on how this might be done. Today, I’m going
to implement some methods that allow to GET
from/POST
to a URL, then do some
basic things with the response using very sentence-like code. Specifically, I’m
going to end up with:
Ajax.GET('/api').then.insertInto('#blog-excerpt').and.evalScripts()
(I hope you’re starting to see why I wrote ChainCollector: code clarity is something that’s very important to me, especially when you’re working in a team that covers the whole web software stack and many of them don’t know JavaScript.) I’m not sure if this exactly answers Saq’s problem, but I hope it will illustrate how you might begin using ChainCollector to solve issues like this.
Okay, the first thing we’re going to need is a new class that provides us with
methods to use in the chain above. Let’s call it ChainableRequest
. I’m using
Prototype today, but hopefully non-Prototype users will be able to follow along.
Ajax.ChainableRequest = Class.create();
Object.extend(Ajax.ChainableRequest.prototype, {
initialize: function(verb, url) {
this.chain = new ChainCollector(this);
this.request = new Ajax.Request(url, {
method: verb,
onComplete: function(transport) {
this.response = transport;
this.chain.fire(this);
}.bind(this)
});
}
});
This class takes two arguments to initialize its instances: an HTTP verb (GET
or POST
) and a URL. It sets up a new ChainCollector
(which will inherit any
methods we add to ChainableRequest
) and sets off an Ajax request to the URL.
It registers a callback that tells the request to fire the chain when the
request completes.
So, onto the methods that we want to add to the chain. We need a method that
inserts the response into some elements on the page. I want this method to
accept a CSS selector, an element reference, or an array of element references.
For each element found, it strips any scripts out of the response and inserts
the remainder into the element. Note how each method returns the
ChainableRequest
object for chaining purposes.
Object.extend(Ajax.ChainableRequest.prototype, {
insertInto: function(elements) {
if (!this.response) return this;
if (typeof elements == 'string') elements = $$(elements);
if (!(elements instanceof Array)) elements = [elements];
elements.each(function(element) {
element.innerHTML = this.response.responseText.stripScripts();
}.bind(this));
return this;
}
});
And, we need a method to evaluate the scripts in the response. You’ll see that
Prototype uses a setTimeout
in some cases to do this, in case the document
hasn’t finished updating the DOM in response to an innerHTML
change.
Object.extend(Ajax.ChainableRequest.prototype, {
evalScripts: function() {
if (!this.response) return this;
setTimeout(function() {
this.response.responseText.evalScripts();
}.bind(this), 10);
return this;
}
});
The final piece of the puzzle is to create methods for any HTTP verbs you want
to use. Note that I’m using capitals because that’s the convention with HTTP
verbs, and also because you might want to go on an implement PUT
and DELETE
(supported by YUI), and delete
is a reserved word in JavaScript. Each verb
method should create a new ChainableRequest
, then return its chain
property
so you can chain methods into the onComplete
callback.
$w('GET POST').each(function(verb) {
Ajax[verb] = function(url) {
var req = new Ajax.ChainableRequest(verb, url);
return req.chain;
};
});
// Remember to add the required methods to ChainCollector
ChainCollector.addMethods(Ajax.ChainableRequest);
And that just about wraps it up in terms of getting our initial code sentence working. You’d probably want something a lot more flexible than this in real life, but this covers some common uses for Ajax calls that can easily be turned into sentence structures. You can download the ChainableRequest JavaScript class to save yourself copy-pasting all the code from this article.