Graceful degradation of jQuery event handlers

If you saw my post on SharePoint custom ribbon actions, you may have noticed the section where I had to lay out several options for jQuery’s persistent event handlers because I couldn’t know which version of jQuery the solution would be used with. Since I’ve written a few projects that are designed to be inserted into existing sites, I thought it would be a good idea to build a system to easily manage the graceful degradation of these handlers.

[Skip this and go straight to the code on GitHub]

The structure

I decided to build this as a jQuery plugin, so it would be easily available from any jQuery object. I called the function $(...).persist() because that describes the idea very well. The $(...).persist() function is formatted in exactly the same way $(...).on() is when used to create persistent handlers. It takes 3-4 arguments, depending on whether event data needs to be passed.

Note that immediately after the function is called, I store the object passed as this in a local variable so it will be available within the context of a different function.

I also created a custom function to detach these persistent handlers, called $(...).unpersist().

Easy: $(...).on() and $(...).delegate()

Distinguishing between $(...).on() and $(...).delegate() was pretty simple. They both take the same arguments, it was just a matter of rearranging them. From the $(...).persist() code:

And the same was true of removing those handlers with $(...).unpersist(), with the slight exception that $(...).undelegate() doesn’t work if it is passed arguments, but those arguments are all undefined. So I had to build in a special case to call $(...).undelegate() empty if there were no defined arguments.

Where’s $(...).live()?

$(...).live() was the original jQuery persistent event handler, and there’s a reason they did away with it so quickly. It had a lot of drawbacks in both performance and usability, and can only be called by a very particular type of jQuery object. After a few attempts to get $(...).live() functioning in the same way as everything else in $(...).persist(), I realized there was no good way to duplicate the functionality of $(...).on() and $(...).delegate() with $(...).live(), so I left it out in favor of keeping this plugin consistent.

So let’s make it challenging

I think in most cases it would be perfectly acceptable to stop here, and simply require jQuery 1.4.2 (released three years ago) or higher to function with this plugin. However, I’ve seen first hand that sites or templates originally constructed years ago can still be in active development without updating their JavaScript libraries, so it was certainly possible that my code would end up on a site that supported neither $(...).on() nor $(...).delegate().

Also, I kinda just wanted to make a challenge out of this project, so I decided to make it compatible all the way back to jQuery 1.0. This presented some really specific challenges, but it did give me a chance to take a good look at how jQuery has evolved since it was first introduced.

The custom handler

I won’t go through the code of the custom handler in too much detail, but I will point out two aspects important aspects that make the whole thing tick.

The idea behind this custom handler is that it gets attached to the jQuery object that calls the $(...).persist() method, and then checks every matching event to see if the target of that event matches the selector passed to the arguments. Of course, it’s possible that one of the target’s parent elements would match the selector, and in that case the event should fire as well, assuming it was still a child element of one of the members of the jQuery object that made the original call.

I sorted all this out using a surprisingly simple do...while loop that moves up the DOM tree one element at a time until it either matches the selector, is no longer a child of the original jQuery object, or reaches the top of the DOM tree.

Detaching

Being able to attach persistent handlers is only half of the equation here, I needed a method to remove handlers that were applied with this custom function.

I accomplished this by storing the data for each call to $(...).persist() and $(...).unpersist() on the jQuery.cache object (which I had to create if it didn’t already exist, since it wasn’t introduced in the first few versions). Whenever an event bound by the custom $(...).persist() function is fired, it loops through all the $(...).persist() and $(...).unpersist() items on the jQuery.cache object to determine whether or not it should fire.

The object created when attaching a handler:

The object created when detaching a handler:

The loop to determine whether the currently firing event is active:

The result

In the end this turned out to be a pretty nifty plugin. It streamlines selecting between $(...).on() and $(...).delegate(), and ends up adding a pretty important piece of functionality to any version of jQuery prior to 1.4.2. I even managed to squeeze in event data on version 1.0.*, which doesn’t support it natively.

Tagged with:
Posted in Custom Library Functions, GitHub, JavaScript, jQuery

Leave a Reply

Your email address will not be published. Required fields are marked *

*