JS.MethodChain
MethodChain provides a mechanism for storing a sequence of method calls and then executing
that sequence on any object you like. Here’s a quick example:
var chain = new JS.MethodChain();
chain.map(function(s) { return s.toUpperCase() })
.join(', ').replace(/[aeiou]/ig, '_');
chain.fire(['foo', 'bar']) // -> "F__, B_R"
Here, we create a new MethodChain object called chain. We then call three methods on it,
map(), join() and replace(). chain remembers the names of these methods and the arguments
you used, then calls the stored chain on any object you pass to its fire() method.
If you call further methods on chain, they get added to the list:
chain.split(/_+/);
chain.fire(['foo', 'bar']) // -> ["F", ", B", "R"]
How it works
When you call a method like map() on a MethodChain instance, the instance stores the
name of the method and any arguments you pass to it. All of chain’s methods return chain
again, allowing you to chain methods together as shown above.
Any method you want to store in a chain has to exist in MethodChain’s list of method names
in advance (my kingdom for a method_missing feature in JavaScript!), or you will get an
error. MethodChain comes with over 300 method names in place from the JavaScript core API
to get you started. If you find that a method is missing, you can add it like so:
// Add all methods from a class or object
JS.MethodChain.addMethods(jQuery);
// Add methods by name
JS.MethodChain.addMethods(['methodName1', 'someOtherMethod']);
All MethodChain instances will then have the methods you’ve added.
There are a few reserved methods on MethodChain objects that you cannot use for chaining:
_()– one underscore – more on this in a second____()– four underscores, used for storing method callsfire()– executes the stored chain against its argumenttoFunction()– returns a function that fires the chain when called
Changing scope using _()
All chains have a method called _() (that’s one underscore) that you can use to change
the scope of the chain. By default, each method in the chain is called on the return value
of the previous method, but _() lets you insert objects into the chain so that subsequent
methods get called on said object.
_() also lets you insert anonymous functions into the chain. Within each function, this
refers to the return value of the previous method. Putting all this together, you could do:
var chain = new JS.MethodChain();
chain._(document).getElementsByTagName('p')
._(function() { console.log(this) });
When we fire chain, the net effect is the same as:
console.log(document.getElementsByTagName('p'));
If you insert a function into the chain, its return value will be used as the receiving object for the next method call in the chain.
it() and its()
it() and its() are two global functions created by MethodChain that just act as
shorthand for returning new MethodChain objects. You could use them to improve syntax
in conjunction with toFunction() for methods in your code that take functions as arguments.
An example from Ojay:
[some, bunch, of, elements].forEach(it().addClass('foo'));
forEach converts its argument to an iterator function before using it. The more long-winded
way to write the above would be:
[some, bunch, of, elements].forEach(function(element) {
element.addClass('foo');
});
These functions (and indeed the whole idea of MethodChain) were inspired by
Methodphitamine,
a Ruby library for collecting method calls and turning them into Procs -
look to that library if you want more usage ideas.
toFunction()
The toFunction method returns a function that fires the chain against its argument.
For example, taking the first chain we created at the top of this page:
var func = chain.toFunction();
func(['foo', 'bar']) // -> "F__, B_R"
The wait() method
JS.MethodChain adds a method called wait() as an instance method and a class method
to all classes and objects created with JS.Class. This method allows you to delay execution
of a chain against an object for a given about of time.
var Fiddle = new JS.Class({
initialize: function(quantity) {
this.size = quantity;
},
proclaim: function(thing) {
console.log('I have ' + this.size + ' ' + thing + 's!');
}
});
var fid = new Fiddle(99);
// Call proclaim() after 5 seconds
// prints "I have 99 problems!"
fid.wait(5).proclaim('problem');
Further reading
For some background and further usage examples, refer to the articles on ChainCollector
on my blog. ChainCollector is the name I originally gave the MethodChain class.