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

Posted December 30, 2009 at 9:53 AM

Tags: Javascript / DHTML, Ask Ben

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.



Reader Comments

Mar 11, 2010 at 3:23 PM // reply »
1 Comments

WOW...that's what I'm looking for. The code examples are very helpful.

Thanks


Mar 11, 2010 at 3:24 PM // reply »
8,836 Comments

@TripeL,

Awesome :) Glad it was helpful.


Post A Comment

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.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Formatting: <strong>bold</strong> <em>italic<em>







  • Help Wanted - Find Your Next ColdFusion Job
Recent Blog Comments
Sep 9, 2010 at 12:50 AM
New ColdFusion Error: Form Entries Incomplete Or Invalid
Once again you save my day Ben. Just migrated from Railo to CF9 and that error was everywhere, I was on the verge of tears... ... read »
Sep 8, 2010 at 7:28 PM
What ColdFusion Teaches Us About The Ultimate "Roll Your Own" Solution
This is how good enterprise software and good enterprise architecture is built. In addition to the time factor and the abstraction potential, the most compelling reason I've ever found to incorporate ... read »
Sep 8, 2010 at 4:47 PM
Ask Ben: Reading In A File Using CFFile And CFInclude
@Ben, Thanks for the quick reply. That was the idea. The getFileFromPath returns a "52648.tmp" file name which is generated by the server not the actual file name. I'm not sure how to extract t ... read »
Sep 8, 2010 at 4:10 PM
Strange ColdFusion URLDecode() and GetEncoding() Behavior
Yep - too strange since the second arg in urldecode is optional. I did run across the error and googled it and landed safely here. Thanks, Ben! ... read »
Sep 8, 2010 at 3:33 PM
What ColdFusion Teaches Us About The Ultimate "Roll Your Own" Solution
@Steve, @Darren, Excellent point! Keeping a platform API (any API at that matter) allows for a much easier time to swap underlying libraries, or even to build your own. @Rick, @Jacob, @JC, I thin ... read »
JC
Sep 8, 2010 at 2:02 PM
What ColdFusion Teaches Us About The Ultimate "Roll Your Own" Solution
@Jacob -- absolutely. The trick is knowing when it'll take less time to do it yourself than wedge someone else's oval shaped application into your round hole. ... read »
Sep 8, 2010 at 1:49 PM
What ColdFusion Teaches Us About The Ultimate "Roll Your Own" Solution
@Rick, I hope this isn't too off topic but, is this true? I've only been programming in the workplace for 4 years. I often look at what others have done and adapt it to my particular needs. Often t ... read »
Sep 8, 2010 at 12:39 PM
ColdFusion CFMailParam's New "Content" Attribute Is Awesome
Ben, Mine is version 8 and tried to download the update but still did not recognized content attribute in cfmailparam. May be I installed a wrong update, so many of them not sure which one I need wit ... read »