Infallible Logic

23Jan/110

[jQuery] Plugins putting developers in a bind

So I've been working on a Chrome extension for a little while that utilizes jQuery. In the true spirit of being smart and lazy, and taking advantage of the huge community that jQuery has, I've naturally pulled in a number of plugins from other developers.

Of course, I got a little lax on my quality control. I wanted to implement some context menus on some of my elements, so I went to my go-to Chrome extension, Chromed Bird, for inspiration. I like the way his context menus are implemented and wasn't too impressed with a few others that I had checked out. So I headed over to A Beautiful Site to read up on their jQuery Context Menu plugin.

It's pretty decent. It installs quickly, has little configuration, and comes with a really nice default stylesheet that doesn't really need any changes (unless you don't want to use the included images). The first warning I should have noticed was that all of the CSS and JS HTML elements were referenced in uppercase (LI instead of li, A instead of a, etc). Ugh. What is this, 1994? Anyway, whatever.

So I set it up and it was quick and painless. I got to testing it out and it seemed to work like I had intended (I was using it in the same was Chromed Bird does, as a context menu for a tabbed interface). But something odd started to happen. I register click events on a number of anchor tags to spawn new tabs in the interface, and as soon as I would open up the context menu, none of the links would work anymore. Kind of a terrible side effect, when you think about it!

So I delved into the code, and this is what I discovered:

  • It disables the standard context menu, which is fine and actually necessary.
  • When the jQuery context menu opens, it registers a click event on $(document) directly. This is so that you can 'remove' the context menu by clicking elsewhere on the page.
  • Once a menu item is clicked, or the document is clicked, it unregisters those click handlers on the document.

Really straight-forward, when you get down to it. The problem was that he was using a global unbind call.

This apparently has the nasty side-effect of removing ALL click elements that have been attached to any element on the page. jQuery does give you the option to pass either an event as the first option to use to unbind, or the function that was attached to the event to unbind from as the second parameter. However, because of the way the code was set up, this really just wasn't possible. I ended up having to do a very heavy rewrite to get it to work properly (and also to make it work a little more how I thought it should work).

The moral of the story is thus:

If you're developing plugins that other people are going to use, you need to be as unobtrusive as possible with your code.

It's entirely possible this is a bug or change in functionality that's happened over the past couple of years since the original release of the plugin, but it's still a design that could have easily been avoided with a slightly better design.

My updated code is below, though it does include a pretty large change in ideology over the original design. In mine, you create one context menu and attach elements to it that will use it, then remove them if they're not needed anymore. It's somewhat specialized for the way it works in my project, but should be generic enough. It does currently depend on John Resig's Class.extend inheritance model. I also removed anything that wasn't necessary for it to run in Chrome (there were a few IE/Firefox specific things), as well as the keypress functionality that had existed that I also didn't find super useful or necessary to have.

[Edit] Oops, just realized it also uses a variation of Prototype's bind() method for binding the local space of function calls to objects:

Filed under: jQuery No Comments