Let's Make a Framework: Plugins Part 3

2011-03-03 00:00:00 +0000 by Alex R. Young

Welcome to part 52 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. Documentation is
available at turingjs.com.

The init Method

In part 1 of the plugins
section of this tutorial, I mentioned the concept of middleware.
Middleware in libraries like Express is used to
bridge client code with library code, and control flow (there's usually
a next() method passed in which you can call to propagate a
value or continue execution).

In an Express app, it's possible to provide a function to a route which
will determine if the route is accessible. This is commonly used to load
users from the session or forward them to a sign in page.

I've deliberately looked for a case in Turing that would benefit from a
simplified middleware-inspired approach, and I decided to take a look at

Less DOM Reliance

Right now, we can chain DOM-related code:

turing('.selector').click(function() { alert('clicked!'); });

And enumerable methods:

turing([1, 2, 3]).map(function(n) { return n * 10; });

But this is possible thanks to a slightly ugly hack residing in the DOM

// Chained calls
turing.init = function(arg) {
  if (typeof arg === 'string' || typeof arg === 'undefined') {
    // CSS selector
    return new turing.domChain.init(arg);
  } else if (arg && arg.length && turing.enumerable) {
    // A list of some kind
    return turing.enumerable.chain(arg);

I've never liked the way it checks if turing.enumerable is
available, and how everything centres around the DOM module.

Function Registration

Instead, we can do this in turing.core.js:

var middleware = [];

function turing() {
  if (arguments.length > 0) {
    var result;
    for (var i = 0; i < middleware.length; i++) {
      result = middleware[i].apply(turing, arguments);
      // If a value is returned, stop and return it, else keep looping
      if (result) return result;

// This can be overriden by libraries that extend turing(...)
turing.init = function(fn) {

If a "middleware" function returns something, then execution will stop
and this value will be returned from turing().

Because I'm using unshift to add the registered functions
in reverse order, your own application code could extend
turing() in some amusing ways:

turing.init(function(a) { if (a === 'hello') return 'world'; });


// => "world"

The usefulness of this is highly debatable, however.

Built-in Module Extensions

Now each module can do this:

// DOM, which provides turing('.selector')
turing.init(function(arg) {
  if (typeof arg === 'string' || typeof arg === 'undefined') {
    // CSS selector
    return turing.domChain.init(arg);

// DOM ready in the events module, which provides turing(function() {} );
turing.init(function(arg) {
  if (arguments.length === 1
      && typeof arguments[0] === 'function') {

// Enumerable module, which provides turing([1, 2, 3]).map(function() {});
turing.init(function(arg) {
  if (arg.hasOwnProperty.length && typeof arg !== 'string') {
    return turing.enumerable.chain(arg);

Granted, this isn't as sophisticated as the middleware provided by
Connect and Express, but it does give us a neater way of extending
turing() dynamically, and plugins or other code could also
extend it.


Each module is now responsible for how it extends turing()
by allowing it to look at the arguments. This has allowed me to tidy up
Turing's internal code, but I'm not convinced it's useful outside of
this. It did allow me to make an amusing Hello, World example, though.