Demystifying custom events in jQuery

categories: howto, javascript, jquery

This article originally appeared in the May 2009 issue of JSMag.

We’re all familiar with the basic events — click, mouseover, focus, blur, submit, etc. — that we can latch on to as a user interacts with the browser. Custom events open up a whole new world of event-driven programming. In this article, we’ll use jQuery’s custom events system to make a simple Twitter search application, but the general concepts should apply to any framework that supports custom events.

I confess: it took me a long time to decide to learn about custom events. The built-in events seemed to suit my needs just fine, and it was difficult to understand why I’d want to start adding my own.

Boy, was I missing out.

It turns out that custom events offer a whole new way of thinking about event-driven JavaScript. Instead of focusing on the element that triggers an action, custom events put the spotlight on the element being acted upon. This brings a bevy of benefits, including:

Why should you care? An example is probably the best way to explain.

Suppose you have a lightbulb in a room in a house. The lightbulb is currently turned on, and it’s controlled by two three-way switches and a clapper, as shown here:

 
<div class="room" id="kitchen">
<div class="lightbulb on"></div>
<div class="switch"></div>
<div class="switch"></div>
<div class="clapper"></div>
</div>
 

Triggering the clapper or either of the switches will change the state of the lightbulb. The switches and the clapper don’t care what state the lightbulb is in; they just want to change the state.

Without custom events, you might write some code like this:

 
$('.switch, .clapper').click(function() {
	var $light = $(this).parent().find('.lightbulb');
	if ($light.hasClass('on')) {
		$light.removeClass('on').addClass('off');
	} else {
		$light.removeClass('off').addClass('on');
	}
});
 

With custom events, your code might look more like this:

 
$('.lightbulb').bind('changeState', function(e) {
	var $light = $(this);
	if ($light.hasClass('on')) {
		$light.removeClass('on').addClass('off');
	} else {
		$light.removeClass('off').addClass('on');
	}
});
$('.switch, .clapper').click(function() {
	$(this).parent().find('.lightbulb').trigger('changeState');
});
 

This last bit of code is not that exciting, but something important has happened: we’ve moved the behavior of the lightbulb to the lightbulb, and away from the switches and the clapper.

Let’s make our example a little more interesting. We’ll add another room to our house, along with a master switch, as shown here:

 
<div class="room" id="kitchen">
<div class="lightbulb on"></div>
<div class="switch"></div>
<div class="switch"></div>
<div class="clapper"></div>
</div>
<div class="room" id="bedroom">
<div class="lightbulb on"></div>
<div class="switch"></div>
<div class="switch"></div>
<div class="clapper"></div>
</div>
<div id="master_switch"></div>
 

If there are any lights on in the house, we want the master switch to turn all the lights off; otherwise, we want it to turn all lights on. To accomplish this, we’ll add two more custom events to the lightbulbs: turnOn and turnOff. We’ll make use of them in the changeState custom event, and use some logic to decide which one the master switch should trigger:

 
$('.lightbulb').
bind('changeState', function(e) {
	var $light = $(this);
	if ($light.hasClass('on')) {
		$light.trigger('turnOff');
	} else {
		$light.trigger('turnOn');
	}
}).
bind('turnOn', function(e) {
	$(this).removeClass('off').addClass('on');
}).
bind('turnOff', function(e) {
	$(this).removeClass('off').addClass('on');
});
 
$('.switch, .clapper').click(function() {
	$(this).parent().find('.lightbulb').trigger('changeState');
});
 
$('#master_switch').click(function() {
	if ($('.lightbulb.on').length) {
		$('.lightbulb').trigger('turnOff');
	} else {
		$('.lightbulb').trigger('turnOn');
	}
});
 

A bit more interesting, huh? Note how the rules about what the master switch does belongs to the master switch; the rules about how a lightbulb turns on and off belong to the lightbulb. It’s also worth noting that the master switch is able to affect all of the lightbulbs without having to iterate over them — it just triggers an event on all elements that have the class “lightbulb”. This isn’t critical to the example, but if we were to perform more elaborate manipulations of each lightbulb than adding and removing a class, this would be an important benefit of custom events, as you’ll see in the rest of the article.

(If you’re accustomed to object-oriented programming, you may find it useful to think of custom events as methods of objects. Loosely speaking, the object to which the method belongs is created via the jQuery selector. Binding the changeState custom event to all $('.light') elements is akin to having a class called Light with a method of changeState(), and then instantiating new Light objects for each element with a classname of light.)

jQuery Event Primer

Before we dive in, a couple of things we need to recap. In the world of custom events, there are two important jQuery methods: .bind() and .trigger(). I encourage you to read the jQuery docs for details, but basically:

Confused? I don’t blame you. Read on and I’ll try to clear some things up.

Our Mission

To demonstrate the power of custom events, we’re going to create a simple tool for searching Twitter. The tool will offer several ways for a user to add search terms to the display: by entering a search term in a text box, by entering multiple search terms in the URL, and by querying Twitter for trending terms.

The results for each term will be shown in a results container; these containers will be able to be expanded, collapsed, refreshed, and removed, either individually or all at once.

When we’re done, it will look like this:

The Setup

We’ll start with some basic HTML:

 
<h1>Twitter Search</h1>
<input type="button" id="get_trends"
	value="Load Trending Terms" />
<form>
<input type="text" class="input_text"
		id="search_term" />
<input type="submit" class="input_submit"
		value="Add Search Term" />
</form>
<div id="twitter">
<div class="template results">
<h2>Search Results for
		<span class="search_term"></span></h2>
</div>
</div>
 

This gives us a container (#twitter) for our widget, a template for our results containers (hidden via CSS), and a simple form where users can input a search term. (For the sake of simplicity, we’re going to assume that our application is JavaScript-only and that our users will always have CSS.)

First, the setup. We’ll build an $actions object that we’ll use later to create the buttons in each results container, and we’ll also create a global search_terms object so we can store a list of search terms that are being displayed on the page.
Next, we’ll do our custom event binding. There are two types of objects we’ll want to act on: the results containers, and the Twitter container.

The Results Containers

The results containers are the heart of the application. We’ll create a setupResults() plugin that will prepare each results container once it’s added to the Twitter container. Among other things, it will bind the custom events for each container and add the action buttons at the top right of each container. Each results container will have the following custom events:

The plugin is also responsible for adding the action buttons to the container by cloning the $actions object that was created earlier. It binds a click event to each action’s list item, and uses the list item’s class to determine which custom event will be triggered on the corresponding results container.

 
// we'll use this every time we add a new results panel,
// so let's build it once and cache it in $actions
var $actions = $('
<ul class="actions" />');
$('
<li class="refresh">Refresh</li>
 
').appendTo($actions);
$('
<li class="remove">Remove</li>
 
').appendTo($actions);
$('
<li class="collapse">Collapse</li>
 
').appendTo($actions);
 
// this is where we'll keep track of which search terms
// are shown on the page already
var search_terms = {};
 

Here's the setupResults plugin:

 
$.fn.setupResults = function(settings) {
	return $(this).each(function() {
 
		var $results = $(this);
		var $actions = settings.actions;
		var term = settings.term;
 
		// change the "Search results for" text
		$results.find('span.search_term').text(term);
 
		// bind custom events for results box
		$results. 
 
		// the "refresh" event fetches
		// the latest content for the term
		bind('refresh', function(e) {
			// indicate that the results are refreshing
			var $this = $(this).addClass('refreshing');
 
			$this.find('p.tweet').remove();
			$results.append('
<p class="loading">Loading ...
 
');
 
			// get the twitter data using jsonp
			$.getJSON(
				'http://search.twitter.com/search.json?q=' + 					escape(term) + '&rpp=5&callback=?',
				function(json) {
					$this.trigger('populate', [ json ]);
				}
			);
		}).
 
		// the "populate" event takes results
		// in json format
		// and populates the results container
		bind('populate', function(e, json) {
			var results = json.results;
			var $this = $(this);
 
			$this.find('p.loading').remove();
 
			$.each(results, function(i,result) {
				var tweet = '
<p class="tweet">' +
					'<a href="http://twitter.com/' +
					result.from_user +
					'">' +
					result.from_user +
					'</a>: ' +
					result.text +
					' <span class="date">' +
					result.created_at +
					'</span>' +
				'
 
';
				$this.append(tweet);
					});
 
					// indicate that the results
					// are done refreshing
					$this.removeClass('refreshing');
				}).
						// the remove event removes
						// the results from the page
						// after the user confirms the action
						bind('remove', function(e, force) {
							// allow forced removal without confirmation
							if (
								!force &&
								!confirm('Remove panel for term ' + term + '?')
							) {
								return;
							}
							$(this).remove();
 
							// indicate that we no longer
							// have a panel for the term
							search_terms[term] = 0;
						}).
 
						// the collapse event collapses the results so only the
						// header of the results section is showing
						bind('collapse', function(e) {
							$(this).find('li.collapse').removeClass('collapse')
								.addClass('expand').text('Expand');
 
							$(this).addClass('collapsed');
						}).
 
						// the expand event
						bind('expand', function(e) {
							$(this).find('li.expand').removeClass('expand')
								.addClass('collapse').text('Collapse');
 
							$(this).removeClass('collapsed');
						});
 
						if ($actions && $actions.length) {
							// add a clone of $actions to the results panel
							var $a = $actions.clone().prependTo($results);
 
							// use the class of each action to figure out
							// which event it will trigger on the results panel
							$a.find('li').click(function() {
								// pass the li that was clicked to the function
								// so it can be manipulated if needed
								$results.trigger(
									$(this).attr('class'), [ $(this) ]
								);
							});
						}
					});
				};
 

The Twitter Container

The Twitter container itself will have just two custom events:

The Twitter container bindings are shown here:

 
$('#twitter').
bind('getResults', function(e, term) {
	// make sure we don't have a box for this term already
	if (!search_terms[term]) {
		var $this = $(this);
		var $template = $this.find('div.template');
 
		// make a copy of the template div
		// and insert it as the first results box
		$results = $template.clone().
			removeClass('template').
			insertBefore($this.find('div:first')).
			setupResults({
				'term' : term,
				'actions' : $actions
			});
 
		// load the content using the "refresh"
		// custom event that we bound to the results container
		$results.trigger('refresh');
		search_terms[term] = 1;
	}
}).
 
bind('getTrends', function(e) {
	var $this = $(this);
	$.getJSON('http://search.twitter.com/trends.json?callback=?', 		function(json) {
			var trends = json.trends;
			$.each(trends, function(i, trend) {
				$this.trigger('getResults', [ trend.name ]);
			});
		});
});
 

So far, we’ve written a lot of code that does approximately nothing, but that’s OK. By specifying all the behaviors that we want our core objects to have, we’ve created a solid framework for rapidly building out the interface.

Let’s start by hooking up our text input and the “Load Trending Terms” button. For the text input, we’ll make the form submission stop in its tracks using e.preventDefault(), then capture the term that was entered in the input and pass it to the Twitter widget’s getResults event. (Again, you can see how we go about passing data to a triggered event.) Clicking the “Load Trending Terms” will simply trigger the Twitter widget’s getTrends event:

 
$(document).ready(function() {
 
$('form').submit(function(e) {
	e.preventDefault();
	var term = $('#search_term').val();
	$('#twitter').trigger('getResults', [ term ]);
});
 
$('#get_trends').click(function() {
	$('#twitter').trigger('getTrends');
});
 
});
 

Just entering a search term into a text box is boring, of course — the following code shows how we can capture search terms from the URL’s hash (e.g., http://foo.com/index.html#foo,bar):

 
$(document).ready(function() {
 
// pass search terms via URL hash
if (document.location.hash) {
	var terms = document.location.hash.split(',').reverse();
	$.each(terms, function(i,term) {
		$('#twitter').trigger('getResults', [ term ]);
	});
}
 
});
 

By adding a few buttons with the appropriate IDs, we can make it possible to remove, collapse, expand, and refresh all results containers at once, as shown below. For the remove button, note how we’re passing a value of true to the event handler as its second argument, telling the event handler that we don’t want to verify the removal of individual containers.

 
$(document).ready(function() {
 
$('#refresh').click(function(e) {
	$('#twitter div.results').trigger('refresh');
});
 
$('#expand').click(function(e) {
	$('#twitter div.results').trigger('expand');
});
 
$('#collapse').click(function(e) {
	$('#twitter div.results').trigger('collapse');
});
 
$('#remove').click(function(e) {
	if (confirm('Remove all results?')) {
		$('#twitter div.results').
			trigger('remove', [ true ]);
	}
});
});
 

It should be noted that you don’t have to choose one or all results containers — really, you can choose any results containers you want to affect. For example, if for some (strange) reason you wanted to refresh only the first and last results containers, you could do:

 
$('div.results:first, div.results:last').
	trigger('refresh');
 

You can see the entire application, including the full HTML and CSS, at http://www.rebeccamurphey.com/jsmag/custom-events/.

Conclusion

Custom events offer a new way of thinking about your code: they put the emphasis on the target of a behavior, not on the element that triggers it. If you take the time at the outset to spell out the pieces of your application, as well as the behaviors those pieces need to exhibit, custom events can provide a powerful way for you to “talk” to those pieces, either one at a time or en masse. Once the behaviors of a piece have been described, it becomes trivial to trigger those behaviors from anywhere, allowing for rapid creation of and experimentation with interface options. Finally, custom events can enhance code readability and maintainability, by making clear the relationship between an element and its behaviors.

Learn More

Dojo Confessions (Or: How I gave up my jQuery Security Blanket and Lived to Tell the Tale)

categories: dojo, jquery

This is a reprint of an article that originally appeared in the October issue of JSMag.

I recently had the opportunity to architect the front-end of a new web application from scratch, and after years of using jQuery, I decided to give Dojo a try. For a variety of reasons — not least of which was Dojo’s approach to code organization and dependency management — I thought this would be a good project to get some real-world Dojo experience under my belt. What follows is an overview of Dojo from the perspective of an avid jQuery user.

I’ve been using jQuery for years. Its simplicity is seductive; after a while, it kind of writes itself. And maybe that was my problem: I was ready to try something new. Many months ago, I wrote on Twitter that I was interested in learning more about Dojo. Rey Bango, jQuery evangelist, wrote back, and put me in touch with Pete Higgins, the Dojo project lead. Pete proceeded to lobby me for months to give his “unified toolkit” a try.

I dabbled. I read the docs. Pete plied me with sample code to show me what I was missing, and even drove to North Carolina to lead a Dojo camp. In August, I decided it was time to stop dabbling and dive in. I’d just finished writing some essentially library-less JavaScript for a web-based iPhone application, a task that left me much more confident in my knowledge of JavaScript. With the mobile site out of the way, the client was ready to build the desktop version, and I would be in charge of the front end.

It was time to choose a library; this time, I chose Dojo.

Deciding on Dojo

The decision to try a new library on a client project was a tough one — I knew that I could write the basic functionality of the site using jQuery a whole lot faster than I would write it with Dojo, simply because I knew my way around jQuery so much better. Using Dojo would mean I’d be looking up a lot of things, from simple stuff like how to work with DOM selections, to more complicated tasks like how to structure my code for the purposes of reuse and abstraction.

As my work on the project progressed and the deadline neared, I had plenty of second thoughts. A few things convinced me to stick with Dojo when the going got tough:

Getting Started

The first step was assembling my new toy. I opted to use the library via Google’s CDN so I could get up and running as quickly as possible. After that, it was time to figure out how I’d organize my files.

Dojo actively supports namespaces for components, which means you can put your application files in one or more directories and associate your namespaces with those directories. I created a high-level controller file in the root /js directory; it would be responsible for figuring out which functionality was required for a given page (a decision I’ll eventually revisit). Then, I created a directory inside the root /js directory, where I’d put all of the individual files for the various components.

Because I was using the Google-hosted version of Dojo, I had to put a script tag in my header template, before I included the base dojo.js, telling Dojo where to find my local files:

 
<script>var djConfig = { baseUrl : '/static/js/' };</script>
 

Finally, I included a line in my controller file to tell Dojo where to find the namespace I’d be using:

dojo.registerModulePath('myNamespace', '../js/myNamespace');

Figuring out all of these pieces may have been the hardest part of making the switch to Dojo — it was a whole lot more setup than I was used to with jQuery, and though it is all documented, it took a bit of effort to find the details and to get the paths set up correctly. The time it took to get everything working properly was time that I spent wondering whether I’d made a good decision. Once it was working, it was time to write some actual code and try to answer that question.

Get Some Elements, Do Something With Them

Those words sum up the jQuery paradigm. In jQuery, you query the DOM using a CSS selector, and the result of that query is a jQuery object, which you can then operate on using method chains. It’s fairly rare in jQuery to work directly with a DOM element. While Dojo supports this paradigm through its dojo.query() method and the NodeList it returns, it’s common in Dojo to work directly with a DOM element rather than a NodeList.

My initial inclination was to stick with what I knew from jQuery, and to use dojo.query() to get everything I wanted to work with. As I dug in, though, I discovered that it could actually be just as elegant (and less expensive) to work directly with DOM elements, even though they didn’t come with any of the magic of a jQuery object. The syntax for doing so was a bit different — for example, dojo.addClass(myDomElement, ‘foo’) instead of $(myDomElement).addClass(‘foo’) — but the more code I wrote, the more frequently and easily I found myself using the dojo.addClass syntax instead.

Embracing this approach was especially valuable when it came to methods that returned something. For example, the dojo.connect() method (used to connect events to elements, similar to $().bind()) returns a connection object, which can be stored and disconnected later without having to know which element the event was attached to. This is, in a word, awesome. It’s also an example of how Dojo requires you to think somewhat differently about how you write your JavaScript.

Returning a Result For the Win

Along those lines, I had to get used to the fact that a lot of Dojo methods returned objects that I could talk to later. For example, dojo.animateProperty() created an animation object which could later be play()’d. All of the XHR methods — and asynchronous methods in general — returned a “deferred” object, to which I could later add success and failure callbacks.
jQuery does return the native XHR object from its $.ajax() method, so you can technically add callbacks there too. What I liked about Dojo’s deferred approach is that it provides a common, simple interface for interacting with all asynchronous operations, and even lets you define your own.

Getting the hang of how to take advantage of these things took some doing, coming from the more procedural, chained world of jQuery, where just about everything returns a jQuery object. Soon, though, I was setting up animations long before I was ready to play them, and adding callbacks to XHRs after they started.

SMDs: A Unified Way to Talk to the Server

One thing I really wanted to try with Dojo was making use of Service Method Descriptions, or SMDs. An SMD file basically contains information about all of the services provided by a resource. In my case, the resource was the server-side application, which I’d be communicating with to request JSON data.

By creating an SMD file, and then instantiating a new Service based on that file, I could create a single place for managing all the paths and parameters I’d use to get what I needed from the server. When I asked the server for something, the Service I created would return a deferred object, to which I could attach callbacks. In the background, Dojo was just running an XHR, but my individual classes didn’t have to worry about the details — I just had to worry about the name of the method and the parameters it required, and the Service I’d defined and instantiated would take care of the rest.

For my initial work, I just created the SMD file by hand, but eventually it’s easy to see how the SMD could be generated by the server-side application itself.

Here's a sample SMD (normally an SMD would have a lot more services, obviously):

 
{
	transport	: 	'GET',
	envelope 	: 	'PATH',
	target 		: 	'/json',
 
	services 	: 	{
		callouts : {
			parameters : [
				{ name : 'callouts', type : 'string' }
			],
			returns : 'object'
		}
	}
}
 

And here's some abbreviated code that makes use of the SMD:

 
dojo.provide('myProject.Callouts');
 
dojo.require('dijit._Widget');
dojo.require('myProject.Services');
dojo.require('dojox.rpc.Service');
 
dojo.declare('myProject.Callouts', dijit._Widget, {
	cache : {},
 
	services : new dojox.rpc.Service(dojo.moduleUrl('smd', 'json.smd')),
 
	postCreate : function() {
		// ...
	},
	_lookup : function() {
		var val = this.domNode.value;
 
			if (this.cache[val]) {
				this._handleResult(this.cache[val]);
			} else {
				var c = this.services.json.callouts({
					'callouts' : val
				});
 
				c.addCallback(dojo.hitch(this,
					function(resp) {
						this.cache[val] = resp;
					})
				);
 
				c.addCallback(dojo.hitch(this,
					'_handleResult'));
			}
	},
 
	_handleResult : function(resp) {
		// ...
	}
});
 

Dependency Management and Building

I love jQuery, I do, but this is an area where it is sorely lacking. It offers little guidance on how to structure your codebase, how to ensure that everything you need is loaded, or how to build your code into production-ready files. Dojo really shines here, but again, it takes a bit of getting used to.

The base dojo.js includes a ton of basic functionality, but I had to quickly learn to dojo.require() additional functionality as I needed it. In jQuery, you’d do this simply by adding another script tag to your HTML; dojo.require() basically does this for you programmatically, but checks to see that the required code hasn’t already been included first. This means each of your components can require exactly what it needs, and it’s perfectly safe to require the same thing more than once. The flip side of this is that if you forget to require something you need, or if you require it using the wrong name, it’s not always clear exactly where you made the mistake.
Each file that will be dojo.require()’d begins with a dojo.provide() statement, telling the dependency management system that it has, indeed, found the right file. After that, the file can go on to dojo.require() its own dependencies, which Dojo loads before proceeding with the rest of the file’s code. The dojo.provide() method also sets up the object chain along the path; for example, if you dojo.provide(‘a.b.c.d’), you can then safely do a.b.c.d.e = { ... }.

When it comes time to assemble your individual files into production-ready, combined, minified files, Dojo’s build system is able to parse dojo.require() statements and automatically include the appropriate files; with jQuery, this is a much more manual process that can be difficult to maintain.

Creating a build wasn’t as straightforward as I’d hoped it would be, and I stumbed a lot along the way. It took a bit of doing to get all of the paths just right, and to figure out how to have a repeatable build process that we could roll up into our full release process. The payoff was big, though: I could keep my files organized how I wanted them, but only serve one file in production. The build system figured out the steps in between.

Organization, Inheritance and Abstraction

As I mentioned above, code organization has been a big issue for me with jQuery. I’ve developed some patterns that I use to keep my code sane, but plenty of other jQuery developers have not, which can make working with other people’s code rather painful. While it’s certainly possible to write procedural, disorganized code with Dojo, dojo.declare() provides a clear way to avoid it.

I talked earlier about creating a namespace for my code and an associated directory. When it came time to start writing, I created individual files in that directory for each component on the page. Inside each file, I indicated which component the file was providing via dojo.provide(), loaded any dependencies via dojo.require(), and then created a class in the namespace using dojo.declare().

The dojo.declare() method takes three arguments: the name of the class you want to create (‘myNamespace.Thinger’), other classes you want to “mix in” to your new class (if any — this argument can be null, a single class, or an array of classes), and, lastly, an object that defines the class methods and properties. The result is a class that can be instantiated using new myNamespace.Thinger(); the object that’s created encapsulates all the behaviors and states associated with a particular Thinger, and you can have as many instances of Thinger as you want.

The mixing in thing is huge, because it lets you have a class that incorporates methods defined in another class. For example, I created a class called myNamespace.Toggler that would show either the first item in a list or all of the items in a list; clicking on the first list item would toggle between the behaviors. Once the myNamespace.Toggler class was created, other classes could inherit its behavior simply by passing a reference to the myNamespace.Toggler class as the second argument of dojo.declare(). I was able to encapsulate the Toggler behavior in a reusable way, and keep the code for the classes that inherited the Toggler behavior nice and clean.

Event Management

I mentioned earlier that Dojo has a slightly different take on event binding than jQuery. I should also say that you can bind events to entire NodeLists (the result of dojo.query()) if you want, using .connect() (or convenience methods like .onclick(), etc.). However, if you want a reference to the connection for later use, dojo.connect() is your friend.

I created a component using dojo.declare() that was responsible for managing user interaction with a star rating UI element. I used dojo.connect() to hook up some mouseover/mouseout behaviors to the element, and stored the returned connections as properties of the component. When the element was clicked, I registered the rating, and I wanted the mouseover/mouseout behaviors to go away; eliminating them was simply a matter of dojo.disconnect()-ing the stored connections.

 
dojo.provide('myProject.Stars');
dojo.require('dijit._Widget');
 
dojo.declare('myProject.Stars', dijit._Widget, {
	postCreate : function() {
		this.stars = dojo.query('div', this.domNode)[0];
		this.input = dojo.query('input', this.domNode)[0];
		this.hoverConnections = [];
 
		for (i=1; i<6; i++) {
			var span = dojo.create('span', {
				'data-value' : i,
				'class' : 'star_helper'
			});
 
			this.hoverConnections.push(dojo.connect(span, 'onmouseover', this, '_update'));
			this.hoverConnections.push(dojo.connect(span, 'onmouseout', this, '_reset')); 
 
			this.connect(span, 'onclick', '_vote');
			dojo.place(span, this.stars, 'last');
		}
	},
 
	_update : function(e) {
		var val = dojo.attr(e.currentTarget, 'data-value');
 
		dojo.attr(this.domNode, 'class', 'stars value_' + val);
	},
 
	_reset : function(e) {
		dojo.attr(this.domNode, 'class', 'stars value_0');
	},
 
	_vote : function(e) {
		dojo.forEach(this.hoverConnections, function(connection) {
			dojo.disconnect(connection);
		});
 
		this._update(e);
		this.input.value = dojo.attr(e.currentTarget, 'data-value');
	}
});
 

Publish and Subscribe

With all of the XHR traffic occurring on the page, I wanted a way to shut it off if the user was inactive for a little while, but I didn’t want to write the code for handling that inside every component that used XHR.

Dojo’s pub/sub tools offered the perfect answer. I created a new component whose sole purpose was to watch for inactivity; when it discovered inactivity, it would dojo.publish(‘/user/inactive’). Then, other components — the ones I’d already written, and ones I write in the future — could subscribe to the /user/inactive topic and react accordingly.

Pub/sub is an excellent way to allow this sort of abstract communication between components. The component that publishes a “topic” doesn’t care who’s listening; the component that subscribes to a topic doesn’t care which component sent it. It’s another example of how Dojo leads you to think a bit differently about how you architect your applications — knowing about pub/sub can help you write much more loosely coupled code.

What I Missed from jQuery

jQuery’s event delegation-related methods .live() and .is() were hard to live without. There’s a reasonable way to mimic .is(), but no out-of-the-box replacement for .live() — you end up writing your event delegation yourself. Dojo does have dojo.behavior(), which automatically binds event handlers to elements that match a given selector as they’re added to the page; however, the event handlers are bound to individual elements, and there’s no way to provide a context to the selector that you pass to dojo.behavior().

This may be my noob-ness talking, or maybe I’m just used to the error messages I’d see when I did something wrong with jQuery, but I often found myself feeling that the error messages from Dojo were too cryptic. Worse, sometimes I’d do something wrong and it would seem to fail silently. I spent a lot more time in Firebug tracking down the errors of my ways.

In general, the thing I really missed from jQuery was the “it just works” aspect of the library. I expect that with time I’ll feel that way about Dojo, but in the meantime there are definitely growing pains. I had to constantly remind myself that the way to solve a problem in Dojo might not be the same way I’d solve it in jQuery. More than once I rewrote a slew of code when I discovered some Dojo methodology or approach I hadn’t known about before.

Conclusion

Lest Rey worry that he never should have introduced me to Pete in the first place, fear not: I don’t expect to give up jQuery anytime soon. If anything, I’m excited to see how the library and the community mature and start answering some of the organization and dependency management questions I mentioned above. As a library, jQuery most definitely has its place; it has virtually no barriers to entry and it has helped usher in an era where it’s dead-simple to create rich, interactive websites.

Deciding to use Dojo instead was something of a gamble. I had to convince the project lead that it was a good decision, which was challenging considering the popularity of the jQuery library. He asked lots of pointed questions about the maintainability of the code if I were to leave the project, and those questions were well deserved. If anything, though, I think that choosing Dojo has actually increased the maintainability of the code by presenting clear patterns for organization, abstraction, and dependency management.
Did it take a while to come up to speed with Dojo? For sure. Will a jQuery developer off the street be able to jump right in to the code I wrote? Possibly not. At the end of the day, though, it is just JavaScript, and any skilled JavaScript developer should be able to find their way around. They’ll almost certainly find, given an hour or two, that the code I wrote is easier to follow than some of the jQuery code I’ve run into that doesn’t make use of good organizing principles.

In the meantime, I hope to be working on the project for a while to come, and I expect the trouble I went through to come up to speed on Dojo will pay big dividends as the application I’m working on grows and matures.

Learn More

Introducing yayQuery — A jQuery podcast

categories: jquery, speaking

If you'd like a download:
mp3 audio (30mb), mp4 video (94mb), ogg video (61mb), Vimeo

In this our first episode of the official yayQuery Podcast, Paul Irish, Adam J. Sontag, Alex Sexton and I stayed up way too late on Monday night and had ridiculous amounts of fun talking about:

Perhaps because it was so late when we finished, there was also chair dancing. Make sure you don't miss it, but don't fast-forward to the end or you'll miss the good stuff.

This is our first try with this, and who knows what will become of it, but we're very grateful for any and all feedback. You can find us on Twitter @yayQuery, or on the #jquery IRC channel. Enjoy!

Using Objects to Organize Your Code

categories: howto, jquery

This is a reprint of an article that originally appeared in the March 2009 issue of JSMag.

When you move beyond simple snippets of jQuery and start developing more complex user interactions, your code can quickly become unwieldy and difficult to debug. This article shows you how to start thinking about these interactions in terms of the bits of behavior the feature comprises, using the object literal pattern.

In the past few years, JavaScript libraries have given beginning developers the ability to add elaborate interactions to their sites. Some, like jQuery, have a syntax so simple that people with zero programming experience can quickly add bells and whistles to their pages.

Adding all those bells and whistles, even some pretty elaborate ones, seems to be just a few Google searches away. A copy here, a paste there, a plugin or a few dozen lines of custom code — the client is duly impressed, and you’re adding jQuery to your resume.

But wait. Now the requirements have changed. Now the thing that needed to work for three elements needs to work for ten. Now your code needs to be reused for a slightly different application where all the IDs are different.

We’ve all seen the snippets that make jQuery (and other libraries) look dead-simple. What those snippets leave out — and hey, they’re just snippets, right? — is how to design your code when your needs go beyond dropping in a plugin or doing some show() and hide().

Introducing the Object Literal pattern

The object literal pattern offers a way to organize code by the behaviors it comprises. It’s also a means to keep your code from “polluting the global namespace,” which is a good practice for all projects and imperative for larger ones. It forces you to think at the outset about what your code will do and what pieces need to be in place in order for you to do it.
An object literal is a way to encapsulate related behaviors, as shown here:

var myObjectLiteral = {
	myBehavior1 : function() {
		/* do something */
	},
 
	myBehavior2 : function() {
		/* do something else */
	}
};

As an artificially simplistic example, suppose you had the jQuery shown in Listing 2 for showing and hiding content when a list item was clicked.

$(document).ready(function() {
	$('#myFeature li').
		append('
<div>').
		each(function() {
			$(this).find('div').
				load('foo.php?item=' + $(this).attr('id'));
		}).
	click(function() {
		$(this).find('div').show();
		$(this).siblings().find('div').hide();
	});
});</div>
 

Simple enough, and yet even in this example there are several things you might want to change later — for example, the way you determine the URL for loading the content, the destination of the loaded content, or the show and hide behavior.

An object literal representation of the feature cleanly separates these aspects. It might look like this:

var myFeature = {
	config : {
		$wrapper : $('#myFeature'),
		container : 'div',
		urlBase : 'foo.php?item='
	},
 
	init : function(config) {
		$.extend(myFeature.config, config);
		myFeature.config.$wrapper.find('li').
			each(function() {
				myFeature.getContent($(this));
			}).
			click(function() {
				myFeature.showContent($(this));
			});
	},
 
	buildUrl : function($li) {
		return myFeature.config.urlBase + $li.attr('id');
	}, 
 
	getContent : function($li) {
		$li.append(myFeature.config.container);
		var url = myFeature.buildUrl($li);
		$li.find(myFeature.config.container).load(url);
	},
 
	showContent : function($li) {
		$li.find('div').show();
		myFeature.hideContent($li.siblings());
	},
 
	hideContent : function($elements) {
		$elements.find('div').hide();
	}
};
 
$(document).ready(function() { myFeature.init(); });

Because the initial example was incredibly simplistic, the object literal incarnation is longer. Truth be told, the object literal method generally won’t save you lines of code. What it will save is headaches. By using an object literal, we’ve broken our code into its logical parts, making it easy to locate the things we might want to change down the road. We’ve made our feature extendable, by providing the ability to pass in overrides to the default configuration. And, we’ve done some limited self-documentation — it’s easy to see at a glance what the feature does. As your needs grow beyond the simplicity of this example the benefits of the structure will become clearer, as you’ll see below.

Note: For an excellent primer on objects, properties, and methods, check out Object-Oriented JavaScript: Create scalable, reusable high-quality JavaScript applications and libraries by Stoyan Stefanov. You may also want to read up on JSON (JavaScript Object Notation).

An in-depth example

Our mission will be to create a UI element that features multiple pieces of content divided into several sections. Clicking on a section will show a list of items in the section; clicking on an item in the left nav will show the item in the content area. Whenever a section is shown, the first item in the section should be shown. The first section should be shown when the page loads.

Step 1: Crafting the HTML

Writing good semantic HTML is a crucial prerequisite to writing good JavaScript, so let’s start by thinking about what the HTML for something like this might look like. The HTML should:

With those guidelines in mind, we’ll start with this html.

Note that we haven’t included any markup to display the section navigation or the item navigation; those pieces will be added by jQuery since they will only work with jQuery; non-JavaScript users will get nice semantic markup. (If there’s anything surprising or confusing in that HTML, now would be a good time to read up on POSH (plain-old semantic HTML) and progressive enhancement.)

Step 2: Scaffolding the Object

My first step in creating an object for a feature is to create “stubs” within the object. Stubs are basically placeholders; they’re the outline for the feature we’re going to build. Our object will have the following methods:

Dojo article in JSMag

categories: dojo, jquery

I've been doing some work with Dojo lately for a client project, and wrote up an article about my experience with the library (coming from a jQuery background) for JSMag this month. If you're interested, definitely check the magazine out -- it's only five bucks, and there are some other great articles in there this month too. I'm a particular fan of the article by Scott Gonzalez about building stateful plugins using the jQuery UI widget factory -- super-neat stuff that I wasn't aware of. Anyway, check it out, and let me know your thoughts.

Inaugural North Carolina jQuery Camp

categories: jquery, speaking

I'm just back from the inaugural North Carolina jQuery Camp at Viget Labs in Durham, N.C., and a couple of people have asked how it went, so I thought I'd write a quick post. I had a whim a few weeks ago to organize the camp; I envisioned an unstructured day where fellow jQuery developers could get together and talk about how they use the library. I figured that since the jQuery Conference had sold out and had a huge waiting list, getting a couple dozen people together on a Saturday in Durham, N.C. wouldn't be that hard.

We had around 25 people show up to the camp today, from novices to experts, including Scott Gonzalez, a contributor to the jQuery UI library. True to my (utter lack of) vision, it was a very unstructured day, but productive and fun I think. My only experience with unconferences was at BarCamp RDU just a few weeks ago, so it took me a bit to get into full go-with-the-flow mode, but when I didn't know what to do, I just asked "what do I do now?" and usually someone would tell me.

We started out by writing some topic ideas up on the wall, and quickly had enough to get started. I split off with the novices to give an intro to the library, while the main room dug into the topics that had been suggested, starting with a talk by Scott about stateful plugins. Up next was Brian Landau from Viget, showing off the code for his mapping plugin and giving an overview of ScrewUnit; then, David Eisinger, also of Viget, showed us some simple strategies for improving perceived performance.

Lunch -- made possible by the generosity of Rich Orris, FireStream Media, Ignite Social Media, and DesignHammer -- was time for informal conversations and demonstrations. Here's Scott and someone whose name I don't remember doing some quality whiteboarding:

IMG_7126

We came back from lunch with a reprise of my presentation from the jQuery Conference about using objects to organize your code, minus FAST FORWARD but otherwise largely intact. I was grateful to have more than 30 minutes this time, and we ended up having some good conversation about code organization in general.

From there it was on to some great show-and-tell -- people are doing excellent things with the library, and doing them with ease -- and then Scott wrapped up the day with an overview of jQuery UI. Probably the biggest hit of the day was Scott's "just one more thing ..." moment, when he showed us a whole new API for using the position method as a setter, coming soon to a plugin near you. Lots of oohs and aahs about that one.

I said at the end of the day that this first camp was really just a proof of concept -- yes, I can get 25 people to show up to talk about jQuery. I'm hoping to do another jQuery camp in January, perhaps. There are a few things I'd like to do differently next time. For one, I'd like to be a little bit more intentional about having more than one session that's suitable for beginners -- a lot of the presentations were super-interesting, but way over the heads of people just getting started.

Also, there were a number of people who signed up who didn't make it, which is a shame because I ordered food expecting a larger turnout. I'd asked people to let me know if they couldn't make it, but alas only a handful did. Next time, I think I'll charge a token amount to attend -- say $10 or so -- so people will feel a bit more committed. If they don't make it, at least their food will be paid for! Charging a few dollars will also help reduce the need for sponsors -- not that I don't love the sponsors, just that it was a bit of work and stress to line them up.

Finally, next time I'd like to be a little bit more intentional about setting up and promoting the event. This time around, I started promoting it before I even had a venue, and shortly after I secured a venue (thank you Viget!) all the slots were filled. Next time around, I might approach that a little bit differently, especially if I can line up a few different venue options.

That's my report. For more pictures, visit Flickr.

On speaking at the 2009 jQuery Conference

categories: jquery, speaking, thoughts

One of my personal goals for this year was to start being part of the solution to the dearth of female speakers at tech events. Though I've talked at a couple of smallish local events over the past few months, this past weekend I got to do it in a big way: I presented a talk on using objects to organize your jQuery code to an audience of around 100 people, more by far than I've ever spoken to before.

[This post isn't so much about the talk itself as my first experience with talking at a conference. If you're interested in the talk, I encourage you to check out the slides, links, and code at the link above.]

I decided I wanted to try to talk at the jQuery conference after I saw the initial very smart, very male speaker lineup. I submitted my talk based on an article I wrote for JSMag earlier this year, and by the time it was all said and done, mine was the second most popular topic and I was slated to have 30 minutes in "the big room."

There is something sort of out-of-body about that moment when I am standing in front of a roomful of people right before I talk -- I had it when I gave my first Refresh talk, when I taught my first jQuery class, when I spoke at my first BarCamp RDU, and yet again this weekend. For that moment, in my head, I am a complete and utter case, and can't quite fathom that I thought this was a good idea. And then I start talking, and then it is OK. And then when it's over, people clap, and I like that part.

Back when I set out to start speaking more, I decided to take an improv class. For six weeks, we practiced being spontaneously funny, and at the end, we got up on stage in front of a bunch of strangers and tried to do it for real. Knowing what that feels like -- what it feels like to run up the aisle like you're excited when really you're terrified because you've never done this before and in real life you sit at a desk all day and talk to no one and what were you thinking? -- makes the thought of talking to a bunch of strangers about what you actually know how to do seem like a completely reasonable thing.

My experience this weekend was nothing short of excellent -- people I barely knew rallied around me throughout the weekend to help me improve my presentation (most notably Chris Williams, organizer of JSConf, to whom I owe many thanks for all the images -- especially the Liger). The audience graciously tolerated the part in the middle where I had to leave the podium to (very publicly) blow my nose. People asked great questions, and audience members gently pointed out things I might want to rethink. With the exception of one creepy off-the-wall comment about my "fine-boned features," the reaction was overwhelmingly positive.

Reliable sources told me that of 300 attendees, approximately 282 were men. I was the only woman to submit a talk. So this is the part where I encourage other women to do the same. I think women, on the whole (of course there are exceptions), are way more inclined than men to think they aren't good enough speakers, that they don't know a topic well enough to tell it to other people. Two truths: one, the speaking skills of the speakers I've seen have been all over the map; two, you'd be surprised how much you actually know about a topic, especially given the right audience. Go speak at a small event -- a local meetup, a Refresh, even a lunch-and-learn at your office. Get to know the people who do speak at events, and discover that they're people just like you. Go out on a limb and try something that's really outside your comfort zone, like improv, and learn about the clapping part that comes in the end. It's worth it.

I’m teaching a jQuery class!

categories: front-end development, javascript, jquery

One of my goals this year was to get up in front of people and talk. To that end, I'm happy to announce that I'll be teaching a jQuery fundamentals class July 29-30 at Carrboro Creative Coworking in Carrboro, N.C.

In my work with jQuery beginners, I often find that the library is so easy to learn that it's possible to skip over the fundamentals of JavaScript. With that in mind, we'll start the class with a high-level overview of key JavaScript principles, including concepts like logic, objects, variable scope, and closures. From there, we'll move on to a thorough overview of the jQuery library -- selecting, traversing, manipulating, effects, events, XHR (Ajax), and plugins. Throughout the class, we'll focus on best practices for writing and organizing jQuery code for easy reuse and refactoring. Participants will leave the class as upstanding members of the jQuery community, armed with a solid understanding of the concepts of both JavaScript and jQuery, and ready to start leveraging the library in their projects.

If you have any questions about the class, drop me an email at rebecca@rebeccamurphey.com, and I hope to see you there!

jQuery validation: Indicate that at least one element in a group is required

categories: jquery, plugins

I had a need today to indicate that at least one of a set of input fields was required. I was hoping there was a direct way to do this in the jQuery validation plugin; while the method isn't quite as straightforward as I was wishing for, it's still fairly simple.

To start with, I put class="required_group" on each of the elements in the group. Then, I added a custom validation method:

 
jQuery.validator.addMethod('required_group', function(val, el) {
        var $module = $(el).parents('div.panel');
        return $module.find('.required_group:filled').length;
});
 

... a custom class rule to take advantage of the new method:

 
jQuery.validator.addClassRules('required_group', {
        'required_group' : true
});
 

... and finally a custom message for the new method:

 
jQuery.validator.messages.required_group = 'Please fill out at least one of these fields.';
 

What I'd love to see is a way to specify a dependent group without using a custom class rule, but I'm not sure what this would look like, as all validation rules are either keyed off an element's class or the presence of the element's name in the rules object. Thoughts? I'm open to the possibility that there's a far better way to solve this --

On gaining respect as a front-end developer

categories: front-end development, thoughts

Someone wrote me today:

Where I work, design is highly valued with the leader of that group being our Creative Director, back end programmers are also highly valued, but front end ... not so much. Partly I think its that I don't toot my horn but I know there are other reasons. At times, my bosses haven't even understood what it is that I do. Back end programmers look down on front end assuming that its trivial or something that should be relegated to compilers.

I was wondering if this is a common thing or more so something that is happening at my particular company, and if you have any advice or pointers on this.

I thought my response might be worth sharing:

I do think this attitude is common but not necessarily the rule. In my experience, I've found that by having a proven value proposition, you can gain converts and respect.

Front end developers are in a unique position to improve page performance (perceived and actual) by using best practices such as the YSlow tests. Front end developers are also in a unique position to help develop templating systems and to write thoughtful CSS, both of which help enable the rapid prototyping and rollout of new features. A focus on results and best practices -- demonstrating that you aren't just pushing pixels around -- is the key.

Back end developers respect people who think like they do. Be mindful of opportunities for abstraction and reuse. Write object-oriented CSS and JavaScript. Craft solutions that are maintainable and documented. Learn and make use of version control systems. Look for opportunities to participate in developer conversations about new features, and understand what the back end developers are up against. They'll appreciate all of this.

Take the time to teach and to learn. Be sure you have at least a passing understanding of the code the back end developers are writing, and leap at opportunities to share your knowledge. I've worked with more than one back end developer who was surprised to discover what all they didn't know about the front end, and through our conversations about how we approached problems, we both learned a lot.

Finally: identify opportunities for quick victories, execute on them, and make the results known. Benchmark before and after. Can you reduce the number of HTTP requests on a page, decreasing both the perceived and actual rendering time? Are you keeping your JavaScript out of the <head> as much as possible, preventing pages from stalling while rendering? Can you write JavaScript that is primed for reuse, and demonstrate opportunities for that reuse? Has your carefully crafted CSS allowed the rapid rollout of a new feature? Don't be afraid to tell these stories -- they'll tend to strengthen your position by clarifying the important role the front-end developer plays in a site.

Good luck :)

« Previous Entries