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 CFUNITED 2008 (Washington, D.C.) with:

Ask Ben: Using jQuery To Act On A Click Event Based On The Target Element

By Ben Nadel on

Hi Ben, How can I disable the click event of a parent element? In my code, I have attached a click event to a row ('<tr>'), however, one of the <td> elements part of the row contains a checkbox. When clicking on a checkbox, I don't want the click event to be performed. How can I disable the click event on the <tr> element? One thing I tried to do is to bind the click event to the non-checkbox <td> elements, however, I didn't manage to make it work yet. Maybe you have a clue or some genius tip :)?

What you are dealing with here is a byproduct of event bubbling within the Document Object Model (DOM). When the user clicks your checkbox, the click event is being responded to by the checkbox, resulting in the toggling of the checkbox. After that, the browser (with jQuery's help) then bubbles that click event up through the node tree, triggering the click event on each of the ancestor nodes. One of those ancestors, as you are experiencing, is the TR that contains the checkbox.

 
 
 
 
 
 
 
 
 
 

One way to prevent this would be to capture the click event on the checkbox and prevent it from bubbling up through the DOM. This is exactly what jQuery's event.stopPropagation() method is used for. However, to do that, we would have to bind click event handlers on all of the checkboxes, which adds a bunch of unnecessary event bindings (which can have both a performance and philosophical cost).

You might be tempted to try and use jQuery's live() event binding to counteract the cost of individual event bindings; however, because live() depends on event propagation, you'll be reacting to the click event on the checkbox after the TR has already reacted to it. As such, you might be able to prevent the default behavior (toggling the checkbox), but you won't be able to stop the event propagation.

To trap the checkbox click event without worrying about binding a click event handler directly to every checkbox, we can add some logic to our TR click event handler. When an event is triggered on a given element, the event object contains two targets: "target" and "currentTarget". The currentTarget is the element on which the event is currently being triggered; this will change as the event bubbles up through the DOM. The target, on the other hand, is the element that triggered the original event; this will always be the same as the event propagates.

Keeping this in mind, we can add some conditional logic to our TR click event handler that examines the event target. If the target element is the checkbox (or the edit input field in my demo), then we are going to treat these as special cases and simply exit out of the TR event handler. To see this in action, take a look at my demo page:

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Acting On A Click Event Based On Then Target Element</title>
  • <script type="text/javascript" src="jquery-1.4a2.js"></script>
  • <script type="text/javascript">
  •  
  • // Create an input that will be used to edit the various
  • // table rows. Since only one row can be edited at a time,
  • // we can create it once and re-use it.
  • var nameInput = $( "<input />" );
  •  
  • // Set the class of the input and set a width.
  • nameInput
  • .addClass( "edit" )
  • .css( "width", "285px" );
  • ;
  •  
  • // Define the event handler for the key-down event on
  • // the input. We are doing this here because the event
  • // will be unbound every time we remove the input from
  • // the page (we will need to re-bind it when we show
  • // the input in the table).
  • function keydownHandler( event ){
  • // We want to finalize the edit with either the
  • // TAB (9) or RETURN key (13).
  • if (
  • (event.keyCode == 9) ||
  • (event.keyCode == 13)
  • ){
  •  
  • // Prevent the default event behavior.
  • event.preventDefault();
  •  
  • // Hide the input - this will commit the edit
  • // to the current cell contents.
  • hideInput();
  •  
  • }
  • }
  •  
  •  
  • // I replace the name within a given cell with an input
  • // field continaing the cell value.
  • function showInput( cell ){
  • // Put the contents of the cell into the input.
  • nameInput.val( $.trim( cell.text() ) );
  •  
  • // Bind the keydown event (this get's implicity
  • // unbound whenever we remove the element from the
  • // DOM tree.
  • nameInput.keydown( keydownHandler );
  •  
  • // Replce the contents of the cell with the input.
  • cell
  • .empty()
  • .append( nameInput )
  • ;
  •  
  • // Select the contents of the input.
  • nameInput.select();
  • }
  •  
  •  
  • // I remove the input from the document (if it is there).
  • function hideInput(){
  • // Check to see if the input is visible (it has a
  • // physical dimention in the document).
  • if (nameInput.is( ":visible" )){
  •  
  • // Get it's parent cell.
  • var cell = nameInput.closest( "td.name" );
  •  
  • // Replace the contents of the cell with the value
  • // supplied in the input.
  • cell.text( nameInput.val() );
  •  
  • }
  • }
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // When the DOM is ready, initialize.
  • jQuery(function( $ ){
  •  
  • // Bind a click handler to each row. This way, when
  • // the row is clicked, we can insert the EDIT input
  • // box.
  • //
  • // NOTE: Typically, I would probably wire the CLICK
  • // event up to the TABLE element itself to avoid
  • // binding an event hanlder on every row. But, to
  • // work *with* the question, I am going on TR.
  • $( "tr.record" ).click(
  • function( event ){
  • // Get a reference to the current row.
  • var row = $( this );
  •  
  • // Get a reference to the target element. This
  • // will be the element that triggered the
  • // original click event.
  • var target = $( event.target );
  •  
  • // We have a few special cases to check for
  • // here. The checkbox, which will simply let
  • // the check happen. And the name input, which
  • // already be in a final state.
  • if (
  • target.is( ":checkbox.delete" ) ||
  • target.is( "input.edit" )
  • ){
  •  
  • // Don't do anything here. Let the checkbox
  • // or edit input click event happen
  • // as it would naturally.
  • return( true );
  •  
  • }
  •  
  •  
  • // ASSERT: At this point, we know that the
  • // click happend in the TR element, but was
  • // not triggered by either the delete checkbox
  • // or the edit input.
  •  
  •  
  • // Get the name cell.
  • var nameCell = row.find( "td.name" );
  •  
  • // Hide the input if it exists.
  • hideInput()
  •  
  • // Show the edit field in the target cell.
  • showInput( nameCell );
  •  
  • }
  • );
  •  
  •  
  • // Bind the click event on the delete button.
  • $( "button" ).click(
  • function( event ){
  • // Remove all the rows with checked boxes.
  • $( "input.delete:checked" )
  • .parents( "tr.record" )
  • .remove()
  • ;
  • }
  • );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Acting On A Click Event Based On The Target Element
  • </h1>
  •  
  • <form>
  •  
  • <table border="1" cellspacing="1" cellpadding="4">
  • <col width="300" />
  • <col width="30" />
  • <thead>
  • <tr>
  • <th align="left">
  • Name
  • </th>
  • <th>
  • Delete<br />
  • </th>
  • </tr>
  • </thead>
  • <tbody>
  • <tr class="record">
  • <td class="name">
  • Sarah
  • </td>
  • <td align="center">
  • <input type="checkbox" class="delete" />
  • </td>
  • </tr>
  • <tr class="record">
  • <td class="name">
  • Tricia
  • </td>
  • <td align="center">
  • <input type="checkbox" class="delete" />
  • </td>
  • </tr>
  • <tr class="record">
  • <td class="name">
  • Joanna
  • </td>
  • <td align="center">
  • <input type="checkbox" class="delete" />
  • </td>
  • </tr>
  • </tbody>
  • </table>
  •  
  • <p>
  • <button type="button">Delete</button>
  • </p>
  •  
  • </form>
  •  
  • </body>
  • </html>

I know this demo is way more complicated than it has to be - I was just trying to mimic what I thought you were trying to do based on the code that you sent me (so that it might be more meaningful). The key part to take away is the IF statement at the top of our TR click event handler:

  • // Get a reference to the target element. This
  • // will be the element that triggered the
  • // original click event.
  • var target = $( event.target );
  •  
  • // We have a few special cases to check for
  • // here. The checkbox, which will simply let
  • // the check happen. And the name input, which
  • // already be in a final state.
  • if (
  • target.is( ":checkbox.delete" ) ||
  • target.is( "input.edit" )
  • ){
  •  
  • // Don't do anything here. Let the checkbox
  • // or edit input click event happen
  • // as it would naturally.
  • return( true );
  •  
  • }

If the target element that triggered the original event is the checkbox or the input, then our event handler simply exits out. In this way, we are capturing the checkbox click event without having to actually bind an event handler to the checkbox. And, if you think about it, this makes the most sense - only the TR cares about the checkbox click; the checkbox itself really couldn't care less.

Anyway, I hope this helps point you in the right direction.


Looking For A New Job?

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader Comments

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.