Track Document-Level Clicks With jQuery MouseDown Events

Posted November 12, 2009 at 9:25 AM by Ben Nadel

Tags: Javascript / DHTML

The other day, I dicussed jQuery's live() event method and why understanding the mechanics behind it was extremely important; the reason being that the live() event method depends completely on the ability for a given event type to bubble up through the DOM (Document Object Model) tree. That post got me thinking about event bubbling in general and how important it is in the construction of rich user-interface interactions. Then, I started to think about all of the times that I have used click-event-handling at the Document level in order to capture the non-clicking of a given element. This made me very nervous.

 
 
 
 
 
 
 
 
 
 

As I talked about in my previous post, tracking anything at the document level requires the bubbling chain for the target event to remain in tact; this holds true for both live() and non-live() event handling. The reason that this realization made me nervous was that I often capture click events below the document level and return(false) from my click event handlers. At the click level, this is appropriate as I don't want the item being clicked to exhibit any default behavior and returning false prevents the browser's default behavior. Returning false, however, also prevents the bubbling of the given click event up through the DOM tree. As such, you can easily imagine a situation where you need to track a click event at the document level, but you leave open the opportunity for the end user to click an element that prevents that event from reaching the document.

To see this in action, take a look at the above video.

Now, I'm just one man, so I'm pretty sure I've messed this up a bunch of times. But, the people on the jQuery development team are complete badasses, so I figured they got this right. As such, I wanted to take a look at how they handled this kind of situation. The scenario that seemed to be most in alignment with what I'm talking about here was the jQuery UI Date Picker widget; the jQuery UI Date Picker widget needs to close when the user clicks on anything that is NOT the Date Picker interface.

To see how they wired this up, I installed the jQuery UI library, created a date picker, and then looked at the events bound to the document object:

$( document ).data( "events" )

As it turns out, they don't track click events at the document level at all - they track mouse down events. Ahhh, very clever! That makes perfect sense; since our UI interactions almost always track click events, it's a safe bet that a mousedown event will never be halted in its propagation path. To put this methodology to the test, I set up this small demo page:

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Track Document-Level Clicks With jQuery MouseDown Events</title>
  • <script type="text/javascript" src="jquery-1.3.2.js"></script>
  • <script type="text/javascript">
  •  
  • jQuery(function( $ ){
  •  
  • // The name of the event that we are using to capture
  • // the extra-popup de-focusing events (so that we know
  • // when to close the pop-up).
  • var eventType = "mousedown";
  •  
  • // Get a collection for the popup.
  • var popup = $( "#popup" );
  •  
  •  
  • // Bind the catching event to the document so that we
  • // can catch any events that bubble up (which would
  • // represent a de-focusing of the pop-up window) such
  • // that we can close the pop-up.
  • $( document ).bind(
  • eventType,
  • function(){
  • // Hide the popup.
  • popup.hide();
  • }
  • );
  •  
  •  
  • // Bind the catching event to the pop-up itself so
  • // that it can stop propagation of the event up to
  • // the document (where the event would take on a
  • // very different meaning - closing the pop-up).
  • popup.bind(
  • eventType,
  • function( event ){
  • event.stopPropagation();
  • }
  • );
  •  
  •  
  • // Hook up the pop-up trigger link to open up the
  • // pop-up window when it is clicked.
  • $( "#popup-trigger" )
  • .attr( "href", "javascript:void( 0 )" )
  • .click(
  • function( event ){
  • // Show or hide the popup window.
  • $( "#popup" ).toggle();
  •  
  • // Cancel default event.
  • return( false );
  • }
  • )
  • ;
  •  
  •  
  • // Hook up the other link to do some processing when
  • // it is clicked; the actually processing here is not
  • // relevant, only that it cancels the default event.
  • $( "#other-link" )
  • .attr( "href", "javascript:void( 0 )" )
  • .click(
  • function( event ){
  • // ...
  • // ... do some non-link processing ...
  • // ...
  •  
  • // Cancel default event.
  • return( false );
  • }
  • )
  • ;
  •  
  • });
  •  
  • </script>
  • <style type="text/css">
  •  
  • #popup {
  • background-color: #F0F0F0 ;
  • border: 1px solid #CCCCCC ;
  • display: none ;
  • height: 100px ;
  • left: 50% ;
  • margin: -55px 0px 0px -155px ;
  • padding: 5px 5px 5px 5px ;
  • position: absolute ;
  • width: 300px ;
  • top: 50% ;
  • z-index: 1000 ;
  • }
  •  
  • </style>
  • </head>
  • <body>
  •  
  • <h1>
  • Track Document-Level Clicks With jQuery MouseDown Events
  • </h1>
  •  
  • <p>
  • <a id="popup-trigger">Show the pop-up window!</a>
  • </p>
  •  
  • <p>
  • I am <a id="other-link">some other link</a>.
  • </p>
  •  
  •  
  • <div id="popup">
  • Hello! I am a pop-up window.
  • </div>
  •  
  • </body>
  • </html>

As you can see above, the two links in the page capture click events and return(false) to override the browser's default click-event handling. But, since we are tracking mousedown events at the document level, rather than click events, we'll know when someone clicks outside of the pop-up, even if they, by chance, click on one of the links.

Very slick; the jQuery UI team definitely thought this one through quite thoroughly.


You Might Also Be Interested In:



Reader Comments

Nov 12, 2009 at 10:18 AM // reply »
21 Comments

Very cool. Once again, thanks for sharing! Also, the Jing audio/visual component is perfect. This kind of thing is conveyed so quick, clear and concise in video form.


Nov 12, 2009 at 10:22 AM // reply »
10,640 Comments

@Jamie,

Thanks my man. The video actually has a 5 minute limit in JING, so I think my last word got cut off :) But it's such a great tool.


Nov 12, 2009 at 1:58 PM // reply »
1 Comments

Ben,

The root issue is that <code>return false</code> both prevents the default action and bubbling.

<code>event.preventDefault()</code> does what you want without relying on <code>mousedown</code> to get there first (<code>click</code> is <code>mousedown</code> followed by <code>mouseup</code>).

Some jQuery specific documentation @ http://docs.jquery.com/Events/jQuery.Event although it's the same as the W3C approach.


Nov 12, 2009 at 2:17 PM // reply »
10,640 Comments

@John,

Right you are, but, if for not other reason than brevity and easy-of-use, I think that people would be more likely to write return(false) than to use preventDefault() and / or stopPropagation(). Especially as it doesn't cause any problems... until it does :)


Nov 13, 2009 at 12:36 PM // reply »
1 Comments

Great post! I would actually recommend that people eschew "return false;" if they really mean preventDefault or stopPropagation but not both. It's a convenient shorthand that's easy to abuse, similar to $(function(){}). I've found myself throwing all my code into DOMReady, when most of it could wait till onload or even some time after.

Couple of questions on your code:
Why are you bothering to overwrite the href property if you'll canceling the default action anyways?

What's up with the parens on return(false)? Just your personal style or is there a functional reason for it?


Nov 13, 2009 at 10:35 PM // reply »
1 Comments

Nice. Keep up the good work!


Nov 14, 2009 at 9:48 AM // reply »
10 Comments

good post Ben...
very informative.

Thanks,
Raghuram Reddy
Certified Professional in Advance Coldfusion
Bangalore India


Nov 15, 2009 at 7:10 PM // reply »
10,640 Comments

@Sasha,

The return(val) is just my personal style. I use every opportunity to add punctuation where available - it makes me happy. Any time something is optional, I generally opt into it so there is no chance of miscommunication.

As far as setting the HREF value, it's simply cause I don't like seeing "#" in the status bar of the browser. Again, just a personal desire.


Nov 19, 2009 at 11:01 PM // reply »
3 Comments

Loved the screencast! Keep them coming!


Nov 20, 2009 at 1:01 PM // reply »
10,640 Comments

@JP,

Thanks, glad you like them. Anything in particular you'd like to see?


Nov 20, 2009 at 1:24 PM // reply »
1 Comments

@Ben,

I'm infinitely more interested in jQuery than ColdFusion.


Nov 20, 2009 at 1:36 PM // reply »
10,640 Comments

@JP,

OK cool, I'll keep it coming.


Nov 30, 2009 at 5:07 PM // reply »
1 Comments

@Ben,

Thanks for this post. It helped me out a bunch. I had a lot of problems with this and in the end I opted to use a brute force approach because my dropdown menu was very complicated. Although not as graceful as your approach this might do the trick for some.

If you want to figure out when to hide your popup then bind some function to document.mousedown. In that function check to see if the event.target contains the popup by using event.target.find(popup). If event.target contains the popup then event.target cannot be the popup. So, call popup.hide().

The "popup" cannot contain itself, so, call popup.hide() when anything else contains popup.


Jan 9, 2010 at 10:32 PM // reply »
10,640 Comments

@Craig,

Glad I could help - sorry for the long delay; sounds like you were on the right track regardless.



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:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Feb 12, 2012 at 3:37 AM
Learning ColdFusion 8: CFImage Part III - Watermarks And Transparency
Hi Ben, Just to ask currently it is placed bottom right corner, if i need to replace the same rendered image on the bottom left side or in the bottom center, how that can be calculated. bottom ce ... read »
Feb 11, 2012 at 9:29 PM
Use jQuery's SlideDown() With Fixed-Width Elements To Prevent Jumping
I can't say how glad I am that I found your post. Thank you very much. ... read »
Feb 10, 2012 at 7:21 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
Update! Instead of $(eval(options.insertAfter)).after(data['insertData']); I now use: var ajaxNode = document.createElement('span'); var parent = $(eval(options.insertAfter))[0].parentNode; ... read »
Feb 10, 2012 at 6:18 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
encountered this same, what I consider, jQuery bug last week. I'm building a site in which I load some content via AJAX. This content contains Linkedin share button placeholders which Linkedin API ne ... read »
Feb 10, 2012 at 11:30 AM
Cross-Origin Resource Sharing (CORS) AJAX Requests Between jQuery And Node.js
After you understand the concepts here, this is an awesome cheatsheet for enabling CORS in just about anything http://enable-cors.org/ ... read »
JM
Feb 10, 2012 at 9:10 AM
My Safari Browser SQLite Database Hello World Example
@Amy, Here is a very good tutorial on how to use JOIN: http://www.sqltutorial.org/sqljoin-innerjoin.aspx ... read »
Feb 10, 2012 at 4:42 AM
Building A Twitter-Inspired RESTful API Architecture In ColdFusion
This is great, very useful Ben. I spotted a small typo in the api.cgm listing: <cfthrow type="Unauthroized" /> Cheers Stefan ... read »
Feb 9, 2012 at 10:35 PM
CFDirectory Filtering Uses Pipe Character For Multiple Filters (Thanks Steve Withington)
I was wondering if there would be a filter you could apply so that you got everything but what you included in the filter. As in show me all docs that are not a .pdf. ... read »