Let's Make a Framework: Chaining

2010-08-26 00:00:00 +0100 by Alex R. Young

Welcome to part 27 of Let's Make a Framework, the ongoing series about
building a JavaScript framework.

If you haven't been following along, these articles are tagged with
lmaf. The project we're creating is called Turing.

Namespaces and Chaining

Throughout this series I've referenced techniques used by widely-used
frameworks like jQuery and Prototype. Prototype packs a lot of
functionality and extends global JavaScript objects to do this.

jQuery takes a different approach. It uses large module-like chunks of
functionality wrapped in closures, then specific parts are exposed
through the jQuery object (we usually write
\$() instead).

Turing has been designed in a similar way to jQuery -- to carefully keep
implementation details private and make functionality available without
polluting global objects.

One drawback of our current implementation is everything takes a lot of
typing. Disregarding the alias we created, code looks like this:

var element = turing.dom.get('#events-test a')[0];
turing.events.add(element, 'click', callback);

// Or...
turing.events.add(turing.dom.get('#events-test a')[0], 'click', callback);

We'd do this in jQuery:

$('#events-test').click(function() {
  // Handler

In this case, click is a shortcut, so the following is

$('#events-test').bind('click', (function() {
  // Handler

This chaining can go on as long as you want. jQuery even provides tools
for popping up to different points in a chained result stack, like

  .css('background-color', 'red')
  .css('background-color', 'green')

This works particularly well when working with DOM traversal.

What this style of API gives us is the safety of namespaced code with
the power and succinctness of prototype hacking, without actually
modifying objects that don't belong to us.


The way this works in jQuery is jQuery() accepts a selector
and returns an array-like jQuery object. The returned object has a
length property, and each element can be accessed with
square brackets. It's not a true JavaScript Array, just
something similar enough.

Each call in the chain is operating on a jQuery object,
which means all of the appropriate methods are available.


We've already seen a combination of aliasing and currying to create a
chainable API in Turing -- check out turing.enumerable.js and
turing.anim.js. In these cases, API calls were chained based on the first parameter -- the first parameter for functions in these classes
was always a certain type, so we could shortcut this and create a chain.

This is really a case of
currying, and is one of those fine examples of a nice bit of functional programming in JavaScript.


jQuery's chaining is based around the DOM, so the previous examples
don't really help. Rather than jumping straight into Turing code, I've
created a little class you can play with called fakeQuery.
This will illustrate what underpins jQuery.

It uses a mock up of the DOM so it has something to query:

var dom = [
  { tag: 'p', innerHTML: 'Test 1', color: 'green' },
  { tag: 'p', innerHTML: 'Test 2', color: 'black' },
  { tag: 'p', innerHTML: 'Test 3', color: 'red' },
  { tag: 'div', innerHTML: 'Name: Bob' }

It's not a particularly accurate representation of the DOM, but it's

This is the core function:

function fakeQuery(selector) {
  return new fakeQuery.fn.init(selector);

It returns a new object based on an init method. The
init method builds an object which can carry around the
current selector and related elements:

fakeQuery.fn = fakeQuery.prototype = {
  init: function(selector) {
    this.selector = selector;
    this.length = 0;
    this.prevObject = null;

    if (!selector) {
      return this;
    } else {
      return this.find(selector);

  find: function(selector) {
    // Finds elements
    // Returns a new fakeQuery

  color: function(value) {
    // Creates a copy of the current elements
    // Changes them
    // Returns a fakeQuery object with these elements

fakeQuery.fn.init.prototype = fakeQuery.fn;

The prevObject property could be used to implement
end() (mentioned above). The full code is in a gist:
fakeQuery. This code uses Node, but you could delete the Node-related parts if you want to run it with Rhino.

Running this code with something like
fakeQuery('p').color('red').elements will produce:

[ { tag: 'p', innerHTML: 'Test 1', color: 'red' }
, { tag: 'p', innerHTML: 'Test 2', color: 'red' }
, { tag: 'p', innerHTML: 'Test 3', color: 'red' }

Overall Pattern

The overall architecture of jQuery is deceptively simple:

The key to the last part is fakeQuery.fn.init.prototype =
. This line is what allows the init
method reference fakeQuery.prototype. You can try running
the code without this if you want to see what happens.


jQuery's design offers an efficient way of traversing and modifying the
DOM. This is attractive to us because we're building a framework without
modifying global objects in the way Prototype

Next week I'll look at building this into Turing. The interesting
challenge here is that as it stands there are no dependencies between
Turing's components.