Organizing jQuery projects: Objects and Namespaces


This is a pattern we’ve been using to organize a big jQuery project that is now composed of 150+ files. When we first started with the project, we kept all page components in their respective jQuery plugins. This was good enough at that time. But as the project grew it became harder and harder to maintain it.

What we did to solve it was go back to basics: use plain JavaScript objects instead of plugins and group them using namespaces.

Plain objects vs plugins

This looks like a step back from making cool plugins but there are actually some benefits. Let’s look at this horizontal slider plugin as an example:

// start slider plugin
(function($) {

  var internal = {
    init: function($el) {
      $el.data('currentPage', 0);
    },

    showPage: function($el, page) {
      // do some fancy animations to show the given page
      $el.data('currentPage', 2);
    },

    currentPage: function($el) {
      return $el.data('currentPage');
    }
  };

  $.fn.slider = function(options) {
    if (typeof options != 'undefined' && options == 'currentPage')
      return internal.currentPage($(this[0]));

    return this.each(function() {
      if (typeof options == 'undefined')
        internal.init($(this));
      else if (options == 'showPage')
        internal.showPage($(this), arguments[1]);
    });
  };

})(jQuery);

// and use it like this:
$('#mydiv').slider(); // assume there's a <div id="mydiv"></div> in the HTML
$('#mydiv').slider('showPage', 2);
alert($('#mydiv').slider('currentPage')); // 2

We’re expecting an output like the Coda Slider. The above code is just a skeleton that shows how we’d probably make a horizontal slider plugin. If we were to convert this into a JavaScript object, it would look like this:

// the slider plugin as an Object
(function() {

  Slider = {
    currentPage: 0,
    $el: null, // a public reference to the HTML element we'll work with

    // this should be called first before doing anything else
    init: function($el) {
      this.$el = $el; // save a reference to the element so we can use it later
      return this;
    },

    showPage: function(page) {
      // do some animations using this.$el
      this.currentPage = page;
    }
  };

})(jQuery);

// and use it like this
var slider = Object.create(Slider).init($('#mydiv')); // instantiate a new Slider object and pass in the <div>
slider.showPage(2);
alert(slider.currentPage); // 2

This pattern takes advantage of prototypal inheritance (Object.create) to create a new instance of Slider. The advantages of this are:

  • It’s much cleaner and easier to understand since you’re sort of creating a “class” and one would just have to create a new instance to use it.
  • It’s not tightly coupled with jQuery. If it ever happens that you’ll have to switch to a different library, it’ll be easier to do so.
  • Object members are exposed (e.g. currentPage), can easily be inspected and used.

The disadvantages of this are:

  • A single instance can operate on a single element only. You can easily work around this though.
  • It’s not a jQuery plugin – it’s not cool (debatable)

Prototypal inheritance

The Object.create() method is not part of JavaScript but is a common pattern used to create a new object whose prototype is the given object. This allows us to inherit the members of Slider into a new (instantiated) object.

if (typeof Object.create !== 'function') {
  Object.create = function (o) {
    function F() {}
    F.prototype = o;
    return new F();
  };
}

There are more discussions about this from Douglas Crockford and Alex Sexton

Namespaces

Now that we have simple objects instead of plugins, we can easily group these into namespaces – objects within objects. We can have a base namespace:

App = {};

And then put our objects under it:

App.Slider = {
  // slider code here
};

// util functions under a base Utils namespace
App.Utils = {
  foo: function() {
    // ...
  },
  bar: function() {
    // ...
  }
};

// group related objects under a single namespace
App.SidebarComponents = {
  About: {
    // ...
  },
  TagCloud: {
    // ...
  },
  CategoryList: {
    // ...
  }
};

// create a slider
var slider = Object.create(App.Slider).init($('#div'));
// call a util function
App.Utils.foo();

This now looks at least more organized and maintainable. And you’d have a nice DOM view (FireBug):

dom

What we can learn from this is that jQuery doesn’t really force you to follow a specific design pattern. It’s excellent at DOM manipulation, but it doesn’t provide much outside that. This is actually good, since this means that you can apply your own design on top of it. jQuery should be part of your project and not the backbone of your project.

Speaking of backbone, Backbone.js is a great complement to jQuery that’s definitely worth checking out.