MethodChain
JS.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.__exec__(['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 __exec__() method.
If you call further methods on chain, they get added to the list:
chain.split(/_+/); chain.__exec__(['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 400
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 calls__exec__()– executes the stored chain against its argumenttoFunction()– returns a function that executes 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 execute 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. For example, many Enumerable
methods accept any object with a toFunction() method as an argument,
allowing expressions like:
[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 executes 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.