Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with:

Ask Ben: Using jQuery's triggerHandler() To Skip Default Behaviors

By Ben Nadel on

Ben, I have a somewhat simple jquery q for you. I have x number of divs... inside each div i have a checkbox next to each checkbox i have a span tag with some text. i want to make it where if they click on the text OR the checkbox, it will highlight that div (addClass('red')). i was looking at using the trigger and when you click on the text portion it will trigger the checkbox click() event,but when i click on the text, the last div and checkbox is checked. If i check on the checkboxes, it works fine. I did have a label tag in there at first but thought it was causing problems. Hope that makes sense. the divs with the checkboxes are generated dynamically based on an ajax call.

The way I read this question, there are three real components:

  1. The form elements are being built programmatically (not in original HTML).
  2. When a checkbox is checked or unchecked, we want to highlight or unhighlight the parent container respectively.
  3. When the span next to the checkbox is clicked, we want this to act like a checkbox click.

As I was going about answering this, I actually ran into some interesting behavior. I think your instincts were correct - when you click on the text, it should trigger the click event on the sibling checkbox. But, when it comes to checkboxes, it seems the order of triggered events is not in our favor. Let's look at the code, and then I will go into more detail:

  • <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  • <html>
  • <head>
  • <title>jQuery triggerHandler() Demo</title>
  •  
  • <style type="text/css">
  •  
  • p.field {
  • border: 1px solid #666666 ;
  • padding: 10px 10px 10px 10px ;
  • }
  •  
  • p.selected-field {
  • background-color: #F0F0F0 ;
  • }
  •  
  • </style>
  •  
  • <script type="text/javascript" src="jquery-1.3.2.js"></script>
  • <script type="text/javascript">
  •  
  • // Build our sample AJAX data.
  • var arrData = [
  • {
  • checkbox: "chb1",
  • span: "Here is some sample text"
  • },
  • {
  • checkbox: "chb2",
  • span: "Here is some sample text"
  • },
  • {
  • checkbox: "chb3",
  • span: "Here is some sample text"
  • }
  • ];
  •  
  •  
  •  
  • // When the DOM has loaded, initialize.
  • $( InitForm );
  •  
  • // Initializes the form.
  • function InitForm(){
  • // Get a handle on the form.
  • jForm = $( "form" );
  •  
  • // Add the dynamic elements.
  • $.each(
  • arrData,
  • function( intIndex, objValue ){
  • // Create a paragraph to hold teh elements.
  • var jPara = $( "<p />" ).addClass( "field" );
  •  
  • // Create the checkbox.
  • var jCheckbox = $(
  • "<input type='checkbox' name='" +
  • objValue.checkbox +
  • "' />"
  • );
  •  
  • // Create the span.
  • var jSpan = $(
  • "<span>" +
  • objValue.span +
  • "</span>"
  • );
  •  
  • // Merge all the elements.
  • jPara
  • .append( jCheckbox )
  • .append( " " )
  • .append( jSpan )
  • .appendTo( jForm )
  • ;
  • }
  • );
  •  
  •  
  • // Now that our elements have been added to the page,
  • // let's find a click handler on each checkbox.
  • jForm.find( ":checkbox" ).click(
  • function( objEvent ){
  • // Get the parent paragraph.
  • jParent = $( this ).parents( "p.field" );
  •  
  • // Check to see if the field is checked. If
  • // so, then add the selected class, otherwise
  • // remove it.
  • if (this.checked){
  •  
  • // Add the selected class.
  • jParent.addClass( "selected-field" );
  •  
  • } else {
  •  
  • // Remove the selected class.
  • jParent.removeClass( "selected-field" );
  •  
  • }
  • }
  • );
  •  
  •  
  • // Now, bind a click handler to the span so that when
  • // it is clicked, it triggers the click on the checkbox.
  • jForm.find( "span" ).click(
  • function( objEvent ){
  • // Get a jquery wrapper for clicked span.
  • var jThis = $( this );
  •  
  • // Get a reference to the checkbox.
  • var jCheckbox = jThis.prev( ":checkbox" );
  •  
  • // Because clicking the checkbox is a default
  • // event, it does not mesh quite well with the
  • // order of events. Therefore, let's manually
  • // check the box and then trigger the handler
  • // (but NOT the default event).
  • jCheckbox[ 0 ].checked = !jCheckbox[ 0 ].checked;
  •  
  • // Trigger click event on sibling checkbox.
  • jCheckbox.triggerHandler( "click" );
  • }
  • );
  • }
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • jQuery triggerHandler() Demo
  • </h1>
  •  
  • <form>
  • <!--- Elements to be added dynamically. --->
  • </form>
  •  
  • </body>
  • </html>

After we build the form elements based on the JSON data (what I assume is coming back in the AJAX call - although if it was HTML, it wouldn't make a difference), we bind a click handler for the checkbox and click handler for the span. The click handler on the checkbox is simple - if the checkbox is checked, then add a selected class to the parent element; if the checkbox is not checked, then remove the selected class from the parent.

The click handler on the span is a bit more complicated. Ideally, what we want to do is simply trigger the click event on the checkbox if the span is clicked. However, what I found out was that the order of "Default Behavior" is not the same. When you click a checkbox, the default behavior (toggling the checked attribute) happens before the click handler is called. However, if you trigger the click handler using jQuery, the default behavior (toggling the checked attribute) happens after the click handler is called. This creates a problem because our click handler on the checkbox is going to assume that the checked attribute has already been toggled.

To get around this issue, what we have to do is toggle the checked attribute of the checkbox from within the span's click handler; then, once this is done, we have to trigger ONLY the jQuery handlers on the checkbox's click event. To do this, rather than call .click() on the checkbox, we call .triggerHandler():

  • jCheckbox.triggerHandler( "click" );

This will execute the click handlers without triggering the default behavior (toggling the checkbox) or bubbling the event.

I think there are ways to make this solution a bit simpler, but I hope this points you in the right direction.




Reader Comments

@Ben:

The better solution would be to use the <label /> tag instead of a <span />. When you map the "for" attribute of the <label /> tag to a checkbox it'll automatically fire the checkbox's events as if you clicked the checkbox directly.

This not only would resolve your workaround for this solution, but would also be semantically correct.

Reply to this Comment

@Dan,

Absolutely; and, funny you bring it up because after this was posted, I emailed back and forth with the "askee" - I suggested that Label would solve the problems.

But, regardless, I am sort of glad that I went down this path as I think it uncovered a bit of a bug.

Reply to this Comment

Yeah I originally had the label tag, but switched to a span tag. I switched back to the label tag and did in fact get it working great.

Thanks!

Reply to this Comment

in jQuery, is there a way to check if a click method on an element was called by a real click or only a trigger

Reply to this Comment

@Eliazer,

If you look at the jQuery source code (that's how I party on a Friday night), you can see that the triggerHandler() method is performing the two tasks (among others) when it creates the event object:

event.preventDefault();
event.stopPropagation();

It then passes this event off to the core trigger() method. So, it's not full-proof, but, you can check your event to see if the event has been stopped:

if (event.isPropagationStopped() && event.isDefaultPrevented()){
.... more likely than not to be triggerHandler ...
}

That's just my best guess; I don't see anything out of the box that really indicates this.

Ultimately, though, you probably don't want to write too much code that has to worry about where the triggering came from.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.