Autonomous Machine

Apply jQuery event handlers once with $.fn.safe()

Today I was in this situation:

  • I had a page that could mutate in a lot of different ways (AJAX, user interaction, etc)
  • I needed a way to execute a block of code against only the new DOM elements on the page (to avoid duplicating the event handlers on the already existing elements)
  • I didn't want to use livequery (delegation would have created its own set of problems)

This is what I came up with:

$('ul.choices a').safe('choices').click(function(event) {
  // code to only execute once for any DOM element
};

$.fn.safe works similarly to jQuery's filter. It returns a jQuery object containing only DOM elements that haven't already been returned for the specified key ('choices' in the above example). So it's safe to execute the code above multiple times without repercussions. Obviously, looping through a large set of elements can be costly, but for smaller sets this simple solution works great.

Here's what's going on behind the scenes:

var $$ = function(param) {
    var node = jQuery(param)[0];
    var id = jQuery.data(node);
    jQuery.cache[id] = jQuery.cache[id] || {};
    jQuery.cache[id].node = node;
    return jQuery.cache[id];
};

$.fn.safe = function(name) {
  return this.filter(function(i, element) {
    $$(this).unsafe = $$(this).unsafe || [];
    if ($.inArray(name, $$(this).unsafe) == -1) {
      $$(this).unsafe.push(name);
      return true;
    } else {
      return false;
    }
  });
};

I'm using the $$ helper I first saw on Yahuda Katz's blog- you should too. It makes working with jQuery's data cache much easier.

See Also

LowPro for Prototype will only apply a given behavior to a DOM element once, but I haven't used it since moving to jQuery for all new projects.