Binding (and unbinding) JQuery Event Handlers in a Complex Web App

I’m commonly placed in a tough situation. Here I am, working on a web related project and I would like to implement a lot of complex features via javascript. JQuery is the first library I’ve used and it has allowed me to at least dream of a functional, yet complex, web application. Typically, the first 25% of the javascript related work is a total breeze, thanks to jquery. The next 30-50% of the work becomes totally convulated, thanks to jquery being my crutch, and never diving in and bothering to understand javascript all these years. The final percent is ussually left on a wish-list of too-buggy-to-deploy and wouldn’t-it-be-nice.

Here I am again, endeavoring to build a complex and functional web application and this time I really want to get it right, and low and behold, I’m beginning to run into one of those typical problems.

Binding Events with JQuery

JQuery really makes this easy. I love it. I remember first starting off and writing a little bit of code like this:

$('.button').click(function() {
  $.get('/some/stuff', function() {
    $('.somestuff').append(....);
  });
});

and what not. Then I discovered that beloved .load() function. Really cut down on a lot of legwork I was doing. But there was always a slight problem. I always ended up with conflicting binds. There were various ways I worked around this, among other problems before .live() was around, but it was always a bit of a conundrum. I think this mostly deals with the approach JQuery takes towards usability. The focus is quickly accessing an object and binding the specified event, nothing more. It makes no effort to force any sort of design paradigm, which leads me to wonder, how should I handle this? I figure the first place to look for inspiration are in libraries I already currently use that solve this sort of design pattern.

Pyglet Event Handling for OpenGL/Python

I’ve used pyglet for a while now. It has a few design patterns for handling events that go something like this:

# first, it allows for decorators over a method
@windows.event
def mouse_press(...):
    pass # and do stuff

# and also a window method to push handlers of class instances
class SomeStuff(object):
    def mouse_press(...):
        pass # and go

some_stuff = SomeStuff()
window.push_handlers(some_stuff)

# and likewise, you can pop those handlers
window.pop_handlers(some_stuff)

The first design pattern doesn’t really solve any particular problem of mine and is pretty much already what is going on using jquery events. But the second pattern is something I would have considered impossible for me to accomplish in javascript, I’ve just never understood the language enough to consider it doable. But recently, I’ve been coming to understand the language a lot more and wrote a small utility for making better use of prototypal inheritance in javascript, js.js, and it struck me that this would be pretty easy to implement.

A Design Paradigm for JQuery Event Handling

First, its important to understand a small bit about the js.js utility. It’s somewhat based off of Crockford’s function that prototyped an empty constructor with a passed in object. The difference here is that js.js calls magic methods (instead of being an empty constructor) and allows one to pass in multiple objects and functions to be prototyped.

The design consists of two parts. A jquery plugin in the form of $.events.push() and $.events.pop() and a prototype inheritance pattern, optionally using the js.js function.

This is the plugin code:

$.extend({
    events: {
        recurse: function(args, action) {
            for (var x in args) {
                var obj = args[x];
                for (var k in obj) {
                    if (typeof obj[k].bind != "undefined") {
                        if (action == "push") {
                            $(obj[k].bind).bind(k, obj[k], obj);
                        } else {
                            $(obj[k].bind).unbind(k, obj[k]);
                        }
                    } else if (typeof obj[k].live != "undefined") {
                        if (action == "push") {
                            $(obj[k].live).live(k, obj[k], obj);
                        } else {
                            $(obj[k].live).die(k, obj[k]);
                        }
                    }
                }
            }
        },
        push: function() {
            this.recurse(arguments, "push");
        },
        pop: function(obj) {
            this.recurse(arguments, "pop");
        }
    }
});

The scenario is this. A lot of ajax events are very similar and would work across different loaded pages/forms. Where necessary, one could override an event to add extra functionality:

controls = {
  index: function(params) { $(...).load(...); },
  form: function(params) { $(...).load(...); },
  save: function(data) { $.post(..., this.index); }
}

events = {
  keyup: function() { ... },
  submit: function() { ...; this.save(data); return false; }
}
events.keyup.live = '.main .filter'; // just demonstrating access to live and bind
events.submit.bind = '.main .form';

user = function() {
  this.init = function() {
    $('a[href=#user]').bind('click', this.click, this);
  }
  this.click = function() {
    highlight(...);
    // do some more stuff;
    this.index();
    this.form();
    
    $.events.pop(one, two, three);
    $.events.push(this);
  }
}

var User = prototype(user, events, controls, object);

$(function() {
  var u = new User({url: '/db/User'});
});

This allows for common parts to be factored out when shared by many different objects. Picture five other objects alongside User. Some might override events and call the original, for example, events.submit.call(this, …).

This is a little rudimentary to start, but its a start none-the-less.