Organizing events with jQuery by Cedric Dugas on August 29, 2011



Following the launch of BackboneFU I wanted to take the time to talk about my current setup when I am not using an MVC framework on the front-end. This will be also part of my presentation at Confoo 2011 (if I’m chosen).

The problem with handling events with jQuery is probably that jQuery itself makes it really easy to make spaghetti code, so only reading the jQuery documentation you are bound to hit a wall at some point. I’m always amazed at junior javascript developers’ code and how they always mess things up if they use jQuery.

Prob #1 Anonymous functions

When you look at the jQuery documentation for events, you would see something like this explaining bind:

$(“#myButton”).bind(“click”, function(){ $ ( this ) . addClass ( "selected" ) } )

It’s not because you can use anonymous function to do everything that you should, use simple objects for that.

var myapp.dashboard = { loadEvents: function ( ) { var _this = this $ ( "#myButton" ) . bind ( "click" , function ( ) { _this. selectedOption ( this ) } ) $ ( "#myButton2" ) . bind ( "click" , function ( ) { _this. doSomething ( this ) } ) } , selectedOption : function ( el ) { $ ( el ) . addClass ( "selected" ) } , doSomething : function ( el ) { } }

What is the deal with “this”

jQuery always assigns the this value on the DOM element when you bind events, pretty useful, but not really good if we use objects and want to call other methods, that is why I pass the this value as a parameter in loadEvents().

Also since the anonymous function changes our scope we need to save the this value doing var _this = this; at the beginning of the method to use it later.

Using an object you will give you a much better code organization, as a bonus you see every events binded in the js file easily.

Give a context

If you are the kind that dump every js files at the beginning of each page, giving a context to your events can be a very good idea.

Personally I really like to use a parent element for that, for example, I know that everything that happens on the dashboard has a parent with the css class dashboard. So I would do something like this:

var myapp.dashboard = { loadEvents: function ( ) { //Check if we are in the right section var $dashboard = $ ( ".dashboard" ) ; if ( ! $dashboard [ 0 ] ) return false ; // Save the this value for later var _this = this ; // Delegate give a context to our events $dashboard. delegate ( "#myButton" , "click" , function ( ) { _this. selectedOption ( this ) } ) ; $dashboard. delegate ( "#myButton2" , "click" , function ( ) { _this. doSomething ( this ) } ) ; } , selectedOption : function ( el ) { $ ( el ) . addClass ( "selected" ) ; } , doSomething : function ( el ) { } } $ ( document ) . ready ( function ( ) { myapp. dashboard . loadEvents ( ) } )

On this line: if(!$(“.dashboard”)[0]) return false; we check if the dashboard element exist, and if it does not, we do not load the events, It can save you a lot of overhead at practically no cost. Then we use delegate instead of bind, so if we want to convert this app to an ajaxy app, events would be automatically destroyed if we removed the dashboard element.

That’s it!

I just wanted to share my current code organization, hope it can give you ideas for your organization too!

Update

Following some commenters ideas, there is a couple of things we could do to improve this.

First, we can use !$dashboard.length instead of !$dashboard[0], little bit clearer.

Lets chain the delegate! Since we always use the same parent this is something we can do. Also, wrapping into an anonymous function is not the best idea in the world, 2 things have been proposed here.

We could use jQuery proxy like this $.proxy(this.selectedOption, this), or we could do something like $dashboard.delegate(“#myButton”,”click”, { dashboard: this }, this.selectedOption);.

So lets check our new formula: