JavaScript MVC: Inheritance

If you’ve ever done a lot of JS development you might find the following situation familiar: you’re loath to open up the bowels of the sweet webapp you wrote because you know inside you will find hundreds of lines of jQuery selectors stacked on top of each other. Try as you might, they just won’t be tamed simply by separating out into distinct functions. Soon you’re lost in your own code, trapped in a Borgesian labyrinth of your own making.

That was one of the first challenges we ran into when I started working at Behance: the decentralized, deframework-ized nature of the client side code. Like many other shops, JavaScript was viewed with equal parts suspicion and distaste. But as our code grew, it became increasingly impossible to continue working the same way as thousands of lines of JS collided tectonically into each other as Everests grew out of merge conflicts.

Fortunately, this is a problem that many before us have encountered and solved. The Model-View-Controller pattern has existed for quite a while. Its primary benefit is the separation of concerns that overwhelms a single-file or single-object approach. While there are several JavaScript MVC solutions already on the net, our solution had to be lightweight, Controller-oriented, and templating engine agnostic. None of the ones we came across were an exact fit for our needs.

First of all, our main concern at the time was Action Method Online, a heavily data-driven app. Every bit of the ‘view’ came from rendering client-side templates, so as to make the app almost entirely service-oriented. For a data-driven app, the complexities of interactions between every subsystem requires a framework with Controllers to represent the entity, whether it is a single Action Step, a post in a discussion, or the whole content area. The first framework we considered, Backbone.js, for all its strengths, is essentially still a Model-View-ViewModel (MVVM) framework. Its primary focus is still on the Model-to-View binding (and vice versa). While that is very powerful, AMO required something different.

Another candidate that fit the bill better was JavaScriptMVC. JavaScriptMVC had controllers, was built off of jQuery, and used many concepts from it. In JavaScriptMVC’s Controllers, property names become jQuery selectors that automatically get bound to their associated functions. That concept became problematic as I thought about the problem more. While it is handy to have, jQuery selectors implies that it is operating on a DOM, which implies that there is already a view. And separation of View and Controller logic was one of the key things I was looking for. Plus, the whole approach was a little too ‘magical’. The framework also had to be readily grasped by other developers, and the more magic, the slower developers come to terms with how it really works.

So with that in mind, and a self-imposed directive to be backward compatible with the code we had already written using Backbone.js, we set out to write our very own JavaScript MVC framework: we call it NBD.js.

The first problem to tackle is the entire class system. JavaScript’s inheritance mechanism is strictly prototypal with no understanding of `class`, and some aspects of traditional inheritance (such as inheritance of static properties). Additionally, without ES5′s Object.create(), the superconstructor of a new class is unnecessarily called just to define a subclass. We had been using Backbone.Model.extend solely for the ability to emulate classical inheritance patterns. However, it was a janky solution. We needed a more systematic approach.

Luckily, John Resig wrote an extension mechanism with similar signature to Backbone.js’s. His version was simple but still included powerful features like the ability to call a superclass’s implementation with this.super(). It served as a great starting point.

/**
 * Behanced Class
 * Built from Simple JS Inheritance by John Resig
 * Addons:
 *  - Static properties
 *  - init() auto-calls super's init()
 *  -- can prevent auto-calling with stat._
 *  - __super__ for Backbone.js compatibility
 *  - Using AMD pattern, with global fallback
 */

/* Simple JavaScript Inheritance
 * By John Resig http://ejohn.org/
 * MIT Licensed.
 */
// Inspired by base2 and Prototype

/*global define, jQuery */
(function(root, factory) {
  if (typeof define === "function" && define.amd) {
    define(function() {
      return (root.Class = factory.apply(this, arguments));
    });
  }
  else {
    root.Class = factory();
  }
}( (jQuery && jQuery.Core) || this, function() {
  "use strict";

  var initializing = false,
      fnTest = /xyz/.test(function(){var xyz;}) ? /\b_super\b/ : /.*/,
      Klass;

  // The base Class implementation (does nothing)
  Klass = function(){};

  // Addon: inherits determines if current class inherits from superclass
  Klass.inherits = function( superclass ) {
    var ancestor;

    if ( typeof this === "function" && typeof superclass === "function" ) {
      ancestor = this.prototype;
      while ( ancestor.constructor.__super__ ) {
        ancestor = ancestor.constructor.__super__;
        if ( ancestor === superclass.prototype ) {
          return true;
        }
      }
    }

    return false;
  };

  // Create a new Class that inherits from this class
  Klass.extend = function extend(prop, stat) {
    var prototype, name, initfn, _super = this.prototype;

    // Instantiate a base class (but only create the instance,
    // don't run the init constructor)
    initializing = true;
    prototype = new this();
    initializing = false;

    function protochain(name, fn, initfn) {
      return function() {
        var hadSuper = this.hasOwnProperty('_super'), tmp = this._super;

        // Add a new ._super() method that is the same method
        // but on the super-class
        this._super = _super[name];

        // Addon: calling up the init chain
        if (initfn) { this._super.apply(this, arguments); }

        // The method only need to be bound temporarily, so we
        // remove it when we're done executing
        try {
          return fn.apply(this, arguments);
        }
        catch(e) {
          // Empty catch for IE 8
        }
        finally {
          if (hadSuper) {this._super = tmp;}
        }
      };
    }

    function chainFn(parent, child) {
      return function() {
        parent.apply(this, arguments);
        return child.apply(this, arguments);
      };
    }

    // Copy the properties over onto the new prototype
    for (name in prop) {
      if ( prop.hasOwnProperty(name) ) {
        // Addon: check for need to call up the chain
        initfn = name === "init" && !(stat && stat.hasOwnProperty("_") && stat._);

        // Check if we're overwriting an existing function
        prototype[name] =
          typeof prop[name] === "function" &&
          typeof _super[name] === "function" &&
          (initfn || fnTest.test(prop[name])) ?
          protochain(name, prop[name], initfn) :
          prop[name];
      }
    }

    // The dummy class constructor
    function Class() {
      // All construction is actually done in the init method
      if ( !initializing && typeof this.init === "function" ) {
        this.init.apply(this, arguments);
      }
    }

    // Addon: copy the superclass's stat properties
    for (name in this) {
      if (this.hasOwnProperty(name)) {
        Class[name] = this[name];
      }
    }

    // Addon: override the provided stat properties
    for (name in stat) {
      if (stat.hasOwnProperty(name)) {
        initfn = name === "init" &&
            !(stat && stat.hasOwnProperty("_") && stat._);
        Class[name] = initfn &&
          typeof Class[name] === "function" &&
          typeof stat[name] === "function" ?
          chainFn(Class[name], stat[name]) :
          stat[name];
      }
    }

    // Populate our constructed prototype object
    Class.prototype = prototype;

    // Enforce the constructor to be what we expect
    Class.prototype.constructor = Class;

    // And make this class extendable
    Class.extend = extend;

    // Addon: for backbone compat
    Class.__super__ = _super;

    return Class;
  };

  return Klass;
}));

I adapted Resig’s simple inheritance mechanism to also provide static inheritance, added the ability to auto-call constructors, and wrapped the whole thing in a shim that will return an AMD-style module or attach it as jQuery.Core.Class (our internal namespace), or if that didn’t exist, simply as the global Class. The icing on the cake was that the call signatures were entirely backward compatible with Backbone.Model.extend(). Putting the new base Class in place was as simple as having the top-level classes extend from the new base Class.

Now we’re ready to write out the Model View and Controller classes! We’ll tackle that on another post, so stay tuned…