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:

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

28 thoughts on “Organizing events with jQuery

  1. For organizing code, using object like this are great. Where they fall short, is performance. One of the benefits of anonymous functions is that they’re super-fast.

    The deeper you nest a method inside an object, the longer it takes to execute it. Again, great for organization, but if you have an app that isn’t performing up to par this would definitely be an area to take a look at.

    Thanks for the write up, it’s always good to see someone else’s setup

    1. If you have an app where the main performance bottleneck is traversing upward through object chains, you are a golden god. Mere mortals can look elsewhere, typically.

  2. By executing you mean on page load? I never had any problem with performance executing events that way, pretty much “instant”.

    That being said I probably never nest an event method more than 1 time from the object it is loaded into.

    Would be curious to see numbers, to me it seems like a micro optimization between 10ms to 50ms but I could be wrong.

  3. Separating anonymous functions like this is great, I only wish there was a way to extend and inherit in a classical style. Other than that, this is the same sort of namespacing I use with jQuery, and I love it.

  4. I ended up to build my own front-end framework to organize my jQuery spagetti ๐Ÿ™‚ The JS module pattern helped a lot.
    Actually Backbone.js is a very good friend for that when building a heavy app.

  5. Nice pattern nekman, You also reminded me that I forgot to save the dashbord jquery collection, oups ๐Ÿ˜›

  6. Good post, Cedric! You’re missing some semicolons near the bind and delegate calls. Was there a reason for this, or just forgetting cause they’re not needed?

  7. np ๐Ÿ™‚

    I saw another thing now:

    if(!$dashboard) return false;

    will always be false…the jQuery function always returns a array so you need to check the length property.

    It should be:
    if(!$dashboard.length) return false;

  8. Great point about using delegate there. As for the context in jQuery callbacks I do this:

    $dashboard.delegate(“#myButton”,”click”, { dashboard: this }, this.selectedOption);

    then in selectedOption I’d do: var dashboard = e.data.dashboard.

    I figure both are pretty synonymous although I like to avoid anon functions. What do you think Cedrik?

      1. Haha yeah I started doing proxy just now on my new app. I never noticed that function before.

        And then it’s important to mention even though ‘this’ wouldn’t be the element anymore, the user can still get it via e.target, since using Adam’s example would make the first argument in selectedOption the event

  9. Typically, I’d use $.proxy (or _.bind or Function.prototype.bind) to set the context and retain access to the event’s target *and* avoid the anonymous wrapping function.

    $dashboard.delegate(“#myButton”,”click”, $.proxy(this.selectedOption, this));

    Also, the last sentence where you state that somehow using delegation allows the events to be cleaned up is true – but handlers would also be cleaned up had they been attached with .bind() or its shortcut methods as well.

  10. You could chain you two delegate functions.
    eg.
    // Delegate give a context to our events
    $dashboard.delegate(“#myButton”,”click”, function() { _this.selectedOption(this) });
    $dashboard.delegate(“#myButton2″,”click”, function() { _this.doSomething(this) });

    change to :
    // Delegate give a context to our events
    $dashboard
    .delegate(“#myButton”,”click”, function() { _this.selectedOption(this) })
    .delegate(“#myButton2″,”click”, function() { _this.doSomething(this) });

    or seeings they are both click events why not .delegate (“#myButton, #myButton2″,”click”,function(){
    //its $(this),attr(‘id’) = myButton do something else do something else.
    })

    1. Having the same issue. My guess is because the ‘el’ parameter being referenced in the callbacks aren’t referencing elements when proxy is used. Instead you have to reference the target. Using the existing code above, $(el) would be replaced with $(el.target).

  11. There is no point in using `.delegate()` if you are attaching a listener to a single element, a regular `.bind()` or `.click()` will be better in this case. (since you are using IDs on the example)

    Also you are not using the `_this` variable anywhere in the second code snippet, so you can just remove it.

    Variables starting with underscore (“_”) are usually used to reference private members, the common naming for storing a reference to the current context is `self`.

    I also don’t agree that storing you application as a plain object is the best approach for this case, I would use the revealing module pattern instead ( http://addyosmani.com/resources/essentialjsdesignpatterns/book/#revealingmodulepatternjavascript ), no need to use `$.proxy()` since all your local variables will be on the same scope during execution.

    Since `doSomething()` and `selectedOption()` don’t use any member of the `dashboard`, there is no need to use `$.proxy()` or to create an unnamed function to store the scope. Since you aren’t calling it from anywhere else on the example there is no real benefit on passing the Element as a parameter.

    Cheers.

    1. here is a refactor which I think avoids most of the problems and give extra flexibility so your dashboard component can keep growing without becoming a “big ball of mud”: https://gist.github.com/1188835/

      sorry if my comments sound a little bit harsh but I just wanted to explain that the technique and code sample provided could be improved even more from the original unnamed functions and that the recommended approach also have its flaws. `$.proxy` is useful but sometimes the problem it solves may not even exist if you coded the application on a different way, I also “never” use `event.data`.

      cheers.

  12. Hey miller,

    You make some valid point, as for bind vs click, its not really true, since you don’t know if your going to add that button later in dashboard and in that case your bind goes into toilet.

    For the _this – this, well I just forgotten to delete it from the last pattern

    I had a look at your example, its nice, but personally I do not like the module pattern using a function and returns, personal taste of course like you. In the long run, I prefer being able to test every function I create, even those that are “private” and that is why I moved away from that pattern.

    It’s just not useful to me, we are a team of 1 or 2 or 3 developers, so we don’t need that pattern overhead.. Ah, i forgot to return my function, can’t unit test that private function without returning it or using a complex system only for unit test that give you access to your objects.

    Still need proxy If i don’t want to lose my scope, but in your example, its useless of course, since you have directly access to other functions of your module.

    1. You can still expose the private methods if you need (I usually expose them starting with an underscore if I need), it’s not about the size of the application or how many developers are working on the project (in many projects I work by myself), it’s just that it is easier for maintenance and also because it avoid scope issues. – More about that matter: https://gist.github.com/1189235

      The `this` keyword is very useful and powerful (specially if you know how to use `apply` and `call` properly) but remember: “with great power, comes great responsibility – Uncle Ben”.

      PS: if the public methods (API) produces the expected output you don’t need to test internal methods, and if you do, than the method probably shouldn’t be private.

  13. You can shortcut $(document).ready(function) with $(function). So

    this:
    $(document).ready(function(){ myapp.dashboard.loadEvents() });

    …can be shortened to :

    $(myapp.dashboard.loadEvents);

  14. Your internet site give me incredibly efficient data. I wish to say thanks to those peoples who contribute to this post. thanks. please keep posting.

Comments are closed.