jQuery pattern: passing event handlers to each()

Thursday, Oct 4, 2007, 13:00

I came across this neat little stratagem while working with jQuery lately, that I thought was worth sharing for its own sake. The first time this occurred to me was when I was refactoring a small jQuery plugin by Scott Sauyet (my code is in the comments); it has since turned up on two other occasions, so it’s demonstrably a common, useful move.

The premise is that often, when you set up a dynamic effect on a page, you also want to effect an initial application of that behaviour at that instant. Let’s say, f.ex., that I have a form with a Yes/No pair of radio buttons with name="is_user", as well as some form fields with class depends-isuser that should be hidden when the No button is selected. At the time I set this up, I also want to show or hide the depends-isuser fields depending on which of the radio buttons is preselected. To accomplish all this, I might write the following code:

var dependent = $( '.depends-isuser' );
var is_user = $( ':radio[name=is_user]' );

is_user.change( function() { dependent[ this.value == 1 ? 'show' : 'hide' ]() } );

if( is_user.filter( '*[checked]' ).val() == 0 )
  dependent.hide();

All very obvious, hopefully: I pull out the dependent elements and the radio buttons, then set a handler on the buttons’ change event to show or hide the dependent elements based on the radio button’s value, and then I also hide the dependent elements right away if the No button was preselected.

Equally obvious is that there is duplication: the code in the event handler and in the initialisation that happens right after the handler is set up does much the same thing. It turns out that this redundancy is very easy to factor out:

var dependent = $( '.depends-isuser' );
var toggle_dependent = function() { dependent[ this.value == 1 ? 'show' : 'hide' ]() };

$( ':radio[name=is_user]' )
  .change( toggle_dependent )
  .each( toggle_dependent );

The key to this sleight of hand is that jQuery’s each() will invoke the function you pass it with “this” set to each selected DOM node in turn – which is exactly what an event handler expects! Invocation via event and via each() provides the same environment (i.e. the node of interest is available in “this”), so the same code can be used in both circumstances.