jQuery Spaghetti! tips and tricks for cleaner code

By March 7, 2012 No Comments

Last week I was at Confoo to present my talk, jQuery Spagetthi. This talk was a collection of best practices with jQuery, since it was somewhat popular why not put that in blog post form. Brace yourself, it’s a long read (you can also download the french slides here).

custom.js cultural problem

It seems a lot of web agencies took the jQuery mantra a bit too far, “less code, do more”. When you work as a front-end developer in one of those agencies and you do website maintenance you always open the js folder in fear.

Probably not what you want to do in your code..

Generally you find a couple of plugins and one file called, custom.js. When you open that, sometimes you are lucky and it only has a couple of lines. Even if the code looks like crap, well, you could recode all this in a couple of hours.

But… Sometimes you are less lucky, and you got a 2000 line mess where you not only get  100 events binded everywhere, you also get an onslaught of selector traversing using .parent() that never stops.

At the end of your day working in that mess, you want to become an alcoholic and change job, illustrated below.

So what we are going to look at is how you can improve on the mess..

Events

jQuery offers you 3 types of event handler, you get

  1. bind(), binded directly on the html element and need to be there when bind() is loaded
  2. live(), that use event delegation and is binded directly on the document
  3. delegate(),like live() use event delegation but with a context, can be binded on any element in the DOM

Event Delegation, (bubbling) – what is this?


Basically, any event fired on any element in the dom can be cached on any parent of that element. jQuery use that feature and for example, you could use delegate on a form to bind events to inputs so the events never leak that particular form. More information on event delegation can be found here.

Abuse Delegate

You should probably always use delegate as it has a lot of advantages over bind() and live().

  1. Bind() is slow when used on a collection of html objects, example, you bind something to 20 inputs, it will have to actually parse all your html documents and bind 20 times your event for each element. Delegate does not suffer from this, you bind it only one time on the form, and let the event delegation magic do the rest.
  2. live() has no context, meaning that since it is binded directly on the document there is a change events leak out. Imagine you use live on a button having a class .btndelete, there is a probable chance somehwere else another developer use that class to, delegate prevent events leaks to the global space like a closure does for variable.

Custom Events

This jQuery feature is generally underused in a general matte. There are several cases where it can make your code better, but first let’s look at the example below.

// Pour ouvrir un lightbox en script en utilisant le plugin colorbox
// Ce script n'est la qu'une fois au travers de notre application
$(document).bind('lightbox.open', function(event, config) {
  $.colorbox(config)
});

// Dans notre script, on ouvre un lightbox
  $(document).trigger('lightbox.open', [{"href":"/popup", "height":400}]);

The first big use case of custom events is to decouple your code from your 3rd party plugins. The best example is the lightbox. The are several lightbox plugins, and it is not because you use one of those now that a better might popup later, or might break with ie10. Using custom events you could create events that you decide and use them in your code and only binding those events to your 3rd party in one place.

Since you bind it only once, changing the plugin is really easy and does not consist of 30 find and replace actions. Plus, you build a standard for your company and make it easier for employees to pick up the standard.

The second use case is for decoupling your js modules. Imagine what happens if you directly call functions from another module and that module is removed from the application? Well, you get js errors in your script, but if you use custom events to do that, there’s no problem.

See the example below:

You want to modify the name of your user but also have another module where the name is printed. In this case you have 2 choices.

One, you modify the html directly from that module, but that’s not very convenient. There is no way of other developers knowing that you’ve done this since nothing shows up on other pages to notify them that your save button modified this html.

Two, You use custom events like “change:name” and you let the left module handle it. If you do that, there is never other ways to change your name – you can just call that change:name event and the left module will handle the rest.

Selector Traversing

One of the big hurdle when you start with jQuery is selector traversing, most people stop at the first option, .parent(). Unfortunately using it is not far from being an anti-pattern. What happens if another dev add a div between your button and your container? Your code is broken, and worse, no one even knows it.

Fortunately there is an easy solution: using closest(), it’s like parent() on steroid. with closest, you pass your target selctor class like this closest(“.container”) and jQuery will magically move up in your dom tree until it hits the selector.

Easy!

Ajax Requests

Since jQuery 1.5, we have a new kid in the block, deferred. Before 1.5 $.ajax was not chainable, and that was not cool! What if you want the same ajax request but with the different success functions? Fortunately, deferred changed all that. Look at the example below:

 

It also added lots of functionalities that it would take too much time to get thought, but one I wanted to talk about was $.when.

$.when

Imagine you have 2 ajax requests at the same time, and you want to execute something after those 2 ajax requests have been executed. Before 1.5 it was really weird; you would have to check if both requests were executed in your success function and save states. So much overhead. With $.when, you just pass your request ajax to it and jQuery does the rest, an example explain it much better:

Code Structure

I know talking about code structure is kind of a picky subject. Everyone does their own thing and well, I do not want to get into that. But for the love of god, do not put a endless series of event directly into the dom ready! Please at least use objects to structure all that. If there is one thing that backbone does well it’s handling events, so lets have a look a typical backbone.js view:

How can we take this and turn it into vanilla javascript? Well it would look like this:

var mailapp.events.users = {
  initialize: function(){
    this.$usersWrapper = $("#usersWrapper");
      if(!this.$usersWrapper.length) this.loadEvents();
  },
  loadEvents: function(){
    var _this = this;
    // launch our modal events
    $.subscribe("modal.complete", function(){ _this.loadPopupEvents(); });

      // Delegate give a context to our events
      this.$usersWrapper
        .delegate("#btnDelete","click", function() { _this.deleteItem(); return false; });
        .delegate("#btnsave","click",   function() { _this.save(); return false;));
        .delegate("#btnModify","click", function() { _this.modify(); return false;));
  },
  loadPopupEvents : function() {
    $(".myPopupForm").validationEngine();
  },
  deleteItem: function(){
    $(this).closest('.item').remove();
  },
  modify: function(){
    var jQitem = $(this).closest('.item');
    jQitem.find("input").css("display"," block");
    jQitem.find(".name").css("display"," none");
  },
  save : function(){
    var jQitem = $(this).closest('.item');
    jQitem.find(".printNom").html($(this).parent().find("input").val());
    jQitem.find("input").css("display"," none");
    jQitem.find(".printNom").css("display"," block");
  }
}

So that is a lot of code. Let’s take it apart.

initialize()

 initialize: function(){
    this.$usersWrapper = $("#usersWrapper");
      if(!this.$usersWrapper.length) this.loadEvents();
  },

I always bind a js module to a part of html document, in init I check if my html container is in the dom. If it is not there, full stop. The rest of the module will not be loaded, and this reduces the overhead and leakage I could get if the module is loaded in another part of the application where it should not be loaded (or if all the js is concentrated in one big js file).

If we have our html container, let’s move to loadEvents().

loadEvents()

 loadEvents: function(){
    var _this = this;
    // launch our modal events
    $.subscribe("modal.complete", function(){ _this.loadPopupEvents(); });

      // Delegate gives a context to our events
      this.$usersWrapper
        .delegate("#btnDelete","click", function() { _this.deleteItem(); return false; });
        .delegate("#btnsave","click",   function() { _this.save(); return false;));
        .delegate("#btnModify","click", function() { _this.modify(); return false;));
  },

All our events in here are loaded with delegate using the html container that I saved earlier in init. We also have a custom event modal.complete that is fired when a lightbox is opened, so it is now really easy to bind events into our lightboxes without any pain.

That’s pretty much the interesting part of the pattern.

Other patterns

The pattern above is one of the easiest and simplest one you can use. Of course there is other solutions out there where you can get private and pseudo protected space. One of my favorite is the revealing pattern, another pattern much underused is the jQuery plugin pattern.

For the most part, every company has their own tabs, sliders, lightbox and other plugins. The problem is, most of the time it is a bunch of code with no settings that is copy pasted from project to project with changes directly applied to the core code.

That’s unfortunate since it really does not help the company grow their js code library. I’m not going to show how the jQuery plugin pattern works, since it is already well explained, but it’s cool and easy have a look!

If you want to go in depth with javascript pattern I recommend Stefan Stefanov book, rightly named Javascript Pattern.

Conclusion

This is a good chunk of my presentation here – I hope it can be useful to some of you guys. As I said in my talk the idea was to give a bunch of advice so you guys can apply what work best for your company and your style and trow the rest in the bin 😉

Please wait...

Author Cedric Dugas

More posts by Cedric Dugas