Your friends at Viget present Extend, a Code & Technology Blog

JavaScript Execution Patterns for Non-Web Apps

A big challege with sites utilizing JavaScript is determining which JS should be executed on each page. Here are some approaches to doing this, along with my preferred method. While libraries like Backbone and Ember have ways of executing JavaScript, this article will mainly focus on executing JavaScript for web sites, not web apps. So I'm talking about more marketing style sites instead of applications. An example would be this very site versus something like Gmail.

For these examples, I'm assuming the following:

  • jQuery is being used on the site
  • All of the JS for the site is concatenated into a single file and loaded on every page.

Everything in document.ready

Before I go into preferred methods, I figured that it makes sense to talk about what you shouldn't do. This is how I used to do it when I first learned how to use jQuery, and I still see some people doing this today.

$(document).ready(function() {
	$('.tabs').tabs();
	
	$('.carousel').carousel();
	
	$('.slider').slider();
});

NO

Just don't do this. Even though jQuery fails silently, there is no reason to just throw stuff at the DOM and hope it sticks. Come up with a plan to execute only the JS that you need for each page.

Dom Based Routing

Paul Irish initially talked about this technique in 2009. The idea is to use the id and class attributes on the body element to determine which JavaScript should be fired.

Let's pretend that your body element looks like this:

<body class="shopping" id="cart">

And you have some JS that looks like this:

SITE = {
	common: {
		init: function(){ ... },
		finalize: function(){ ... }
	},
	shopping: {
		init: function(){ ... },
		cart: function(){ ... },
		category: function(){ ... }
	}
};

The execution code is set up so that it calls the following methods:

SITE.common.init()
SITE.shopping.init()
SITE.shopping.cart()
SITE.common.finalize()

Garber-Irish Implementation

This technique came out of my coworker, Jason Garber, working on a Rails project and had a desire for a little more structure. The idea is to use the Rails controller and action to execute specific JavaScript.

For example, on the users show page, the body element would look like this:

<body data-controller="users" data-action="show">

So you create objects based on controllers with a method for the action. The JavaScript would look like this:

SITE = {
	common: {
		init: function() {}
	},
 
	users: {
		init: function() {},
		index: function() {},
		show: function() {}
	}
};

The execution code is set up to call the following methods:

SITE.common.init();
SITE.users.init();
SITE.users.show();

This pattern has worked great for a number of sites, and I've even used the pattern for non-Rails projects. But, as we have continued to build more responsive and modular sites that are more system-based instead of page-based, the pattern has become less and less ideal.

Feature-Based Execution

This technique takes a feature based approach instead of a page specific approach. The idea is to break each feature out into a separate object, and then add the features that you want to execute as an attribute on the body. Here is an example of what our body element would look like:

<body data-features="timeline tabs filters modal">

Here is how we have each of the features broken out into separate objects:

SITE.features = {
	filters: {
		init: function() {}
	},

	modal: {
		init: function() {}
	},

	tabs: {
		init: function() {}
	},

	timeline: {
		init: function() {}
	}	
};

Then, I typically add an init method onto our SITE.features object to initialize all of the feature based execution:

SITE.features = {
	init: function() {
		var features = $('body').data('features');
		var featuresArray = [];
 
		if(features) {
			featuresArray = features.split(' ');
 
			for(var x = 0, length = featuresArray.length; x < length; x++) {
				var func = featuresArray[x];
 
				if(this[func] && typeof this[func].init === 'function') {
					this[func].init();
				}
			}
		}
	}
};

View as Gist

So when we call SITE.features.init() on document load, we grab the data attribute off of the body, and execute each feature. It would execute the following methods:

SITE.features.timeline.init()
SITE.features.tabs.init()
SITE.features.filters.init()
SITE.features.modal.init()

Feature-Based Execution + Script Loader

An idea that I have been playing with, but haven't implemented yet on a site, is to use a script loader to load features for the page instead of including them in a single concatenated file. Here is what the modified SITE.features.init() method looks like using the yepnope script loader:

SITE.features = {
	init: function() {
		var features = $('body').data('features');
		var featuresArray = [];
 
		if (features) {
			featuresArray = features.split(' ');
 
			for(var x = 0, length = featuresArray.length; x < length; x++) {
				var func = featuresArray[x];
 
				yepnope([{
					load: 'scripts/features/' + func + '.js',
					complete: function () {
						if(this[func] && typeof this[func].init === 'function') {
							this[func].init();
						}
					}
				}]);
			}
		}
	}
};

View as Gist

jQuery Meetup

I recently had the opportunity to give a presentation on this topic, along with jQuery plugin development (go to slide 72 if you want to skip the jQuery plugin development piece) at the DC jQuery Meetup.

What Did We Learn?

Don’t just haphazardly fire a bunch of JS functions that may or may not be used. Come up with a pattern for triggering JS on each page that makes sense for your site.

So what methods have you used for executing JavaScript on sites?


Get More From Viget

Subscribe to get our monthly newsletter and occasional special announcements.