thingswithpixels

Feb 15, 2011

Write a Javascript application without losing your sanity

jQuery is great, isn't it? It makes everything a cinch. Ajax requests? No problem! Cool image gallery effects? No sweat! Popups, tool-tips, and overlays? Owned! jQuery is the veritable sonic screwdriver of our javascript toolkit.

But anyone who has written a medium to large sized application relying heavily on jQuery knows that things can degrade quickly from an organizational standpoint. Once a codebase starts to grow, and functionality becomes complex, you will consistently run into a number of issues that plague the typical jQuery coding practices:

  • Large number of dependencies, that need to be included in order
  • Heavy HTTP payload
  • Code repetition, too depedent on DOM
  • No design patterns or tightly coupled code
  • Polluted global namespace
  • A hodge-podge of plugins that 'just work', even though you don't know what's going on under the hood

So how do we address these problems? Simple: treat your Javascript like you would any other large codebase, and adhere to the same emphasis on organization and best practices.

Sir, please step away from the dollar sign.

The original inspiration for this post was this presentation from Rebecca Murphy at JSConf, speaking about the jQuery 'divide'.

In her talk, Murphey highlights the some of the primary problems that plague modern javascript codebases. But she also takes time to underscore a core reality that many developers forget: jQuery is primarily a DOM and AJAX tool. And that DOM and AJAX is a small sub-set of the overall Javascript platform. With that idea firmly planted in our heads, we need to realize it is not enough to just use jQuery, you need to learn to use Javascript as a language and platform. This means implementing all the same best practices you would with your 'real' application code.

Namespacing is good. Even if it takes slightly longer to type.

The global namespace in Javascript is a terrifying place. In fact, most jQuery plugin authors wrap their code in anonymous functions to avoid leaking variables into the global namespace, or avoiding the dreaded `$` collision.

The whole idea of namespacing was design to stop this problem. Why fight it? It's not hard to implement. While some frameworks and plugins do implement fancy namespacing functionality, the basic approach to declaring and importing namespaces looks like this (as cribbed from Google's Closure library):

var twp = twp || {};
twp.widgets = twp.widgets || {};

twp.widgets.ContentSlider = function() { 
  // constructor
}

Only having a single root-level object ensure that we're not polluting the global window object and we'll avoid naming collisions. You don't need to prefix your class names, or worry about competing with other plugins, or shield your code inside its own closure scope. Just namespace it.

One class, one file. Concat, minify and serve.

Time to stop making excuses for the MegaFile. If you're writing in PHP or Ruby or C# and you will always start to notice code smell when your files expand beyond a few thousand lines. Why should it be any different in javascript? Create a `lib` folder in your javascript folder, and divide your classes into folders and files. Again, simple.

To organize your files, just use the namespace structure we created in the example above.

js/
-- lib/ 
---- Twp/ 
------ widgets/ 
-------- ContentSlider.js

But Bryan, you ask, there's no way I can split up my files like that. How am I supposed to manage importing them all into my page? Well, if you're not minifying your code before sending it out to the user, you should start right now. But during the minification process, you can also take advantage of a build phase. By using a proper build process, you can not only reduce your HTTP payload, but create automated steps for syntax checking and unit testing. Some tools to help you out:

  • HTML5 Boilerplate includes a build scripting using Apache Ant.
  • Google's Closure Compiler will do concatenation and minification
  • JSLint will enforce syntax rules and tell you mean things about your code.
  • unit testing frameworks for Javascript. Have your pick.

The other wonderful benefit of a build process is that could conceivably be used to cherry pick different components and pieces from your library, allowing you to create and customize packages for different uses. You're not required to take the entire library with you when you only need a portion.

jQuery is a third-party library. Treat it like one.

Your application code does not begin and end with jQuery. It is just another tool in your tool chain. It does not need to be part and parcel of your entire application.

Remember that jQuery excels as AJAX and DOM. Don't write your entire application as a plugin. Separation of concerns exists in Javascript-land, too.

When you create a jQuery plugin, you're extending the core jQuery object (via the fn object, a place where jQuery has sanctioned for you to use). This is great, but only to extent that jQuery needs to know your application logic. If we're thinking about our app in MVC terms, jQuery plugins should really only operate as a controller. Say we're building a time-tracking application for our employees. Your plugin shouldn't care about an employee's name is or what they charge per hour. That's domain logic, and should safely encapsulated in a model object.

 
twp.models.Employee = function(name, rate) {
  this.name = name;
  this.rate = rate;
}

twp.models.Employee.prototype.log = function(hours) {
  return hours * this.rate;
}

$.fn.billableHours = function() {
  return this.each(function() {
    var employee = new twp.models.Employee('Bob', '50');
    var hours = $('.hours-worked').value();
    $('#total').value(employee.log(hours));      
  });
}

See what I mean? By taking our domain logic out of the plugin, we've not only simplified jQuery's role, but we've create a reusable model class that can be used across the entire application without having to duplicate logic.

Using Javascript as a platform

Stop reading articles like '25 jQuery Plugins to Make Your Site Rock' or '10 jQuery Techniques for Gorgeous Websites.' Other people's plugins are useful, but only to a point. Master jQuery isn't terribly difficult: effects, DOM manipulation and AJAX all follow the same simple syntactical principles. Your real challenge is in architecture, performance and maintainability. When it comes to the Really Important Stuff, think through the problem and write that code yourself.