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 »
29 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 »
11,238 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 »
11,238 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 »
3 Comments

Nice. Keep up the good work!


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

good post Ben...
very informative.

Thanks,
Raghuram Reddy
Certified Professional in Advance Coldfusion
Bangalore India


Nov 15, 2009 at 7:10 PM // reply »
11,238 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 »
11,238 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 »
11,238 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 »
11,238 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
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 19, 2013 at 2:31 PM
My Experience With AngularJS - The Super-heroic JavaScript MVW Framework
It's funny really just how well that image describes the way I would imagine most people that go with angular for some project is. I have had a similar roller-coaster ride with it as well, but not qu ... read »
May 17, 2013 at 7:42 PM
HashKeyCopier - An AngularJS Utility Class For Merging Cached And Live Data
Ben - thanks so much for posting these Angular articles and findings, they've been a huge help towards learning one of the more 'complex' JavaScript frameworks out there (IMO). I have been using Angu ... read »
May 16, 2013 at 5:01 PM
UPDATE: Parsing CSV Data Files In ColdFusion With csvToArray()
Your code was the closest thing I've found to obtaining some direction for converting ISO fields to values that CF can translate properly. Thank you for posting! ... read »
May 15, 2013 at 10:37 PM
Very Simple Pusher And ColdFusion Powered Chat
hi id making plz easy ... read »
May 15, 2013 at 6:07 PM
Making SOAP Web Service Requests With ColdFusion And CFHTTP
Ben, you once again saved my bacon at work. Thank you, thank you, thank you! ... read »
May 15, 2013 at 4:15 PM
What If All User Interface (UI) Data Came In Reports?
@Josh, Thanks! @Ben, I definitely recommend the David West book "Object Thinking" I've been quoting from. It goes deeply into the philosophy and history of OO programming. His breadth ... read »
May 15, 2013 at 11:36 AM
Ask Ben: Print Part Of A Web Page With jQuery
I found this helpfull when you need to keep (refresh) the original parent page after closing the iframe child print dialog (Hoping you're not using a form at this time so it won't submit again): On ... read »
May 14, 2013 at 7:13 PM
What If All User Interface (UI) Data Came In Reports?
@Jonah, If there's any books you'd recommend on the subject of domain modelling, I'd love to hear it. I just downloaded the free PDF of "Domain Driven Design Quickly". Figured I'd give it ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools