views:

328

answers:

4

Hello,

I would like to add two events to my items.

One for the "background" of the document: $(document).click(function() {do()});

One for the divs in the document: $("div").click(function() {do2()});

The problem is that when I click on a div, both functions, do and do2 are called.

How can I thus tell to call do() only when I click on no element?

Thanks for your help,

J.


A: 

you could try excluding the divs from the document selector.

$(document:not(div)).click(function() {do()});

$("div").click(function() {do2()});

untested though.

edit: realized that my answer is, indeed wrong. I'll keep this here to show the wrong way to do this thing. For the correct ones, look for the upvoted answers.

andyk
+3  A: 

The trick is to return false from event handlers. This will stop the event from propagating further.

This seems to work for me:

<html><head>
<script type="text/javascript" src="http://jqueryjs.googlecode.com/files/jquery-1.2.6.min.js"&gt;&lt;/script&gt;
<script type="text/javascript"><!--
$(document).ready(function() {
    $(document).click(function() {
     alert('document'); return false;
    });
    $('#my_div').click(function() {
     alert('div'); return false;
    });
});
--></script>
<body>
<h1>hello!</h1>
<div id="my_div">this div is special</div>
</body></html>
che
+3  A: 

You have to call stopPropagation(). Andyk and Stans examples all fail for me in firefox.

Here I tested this in firefox and it works.

$("html").click(function(event){
    event.stopPropagation();
    alert('document');
});

$("div").click(function(event){
    event.stopPropagation();
    alert('div');
});
gradbot
nice answer. Thanks for clarifying mine. :)
andyk
;) thanks for being a good sport.
gradbot
+6  A: 

The terms you're looking for here are Event Propagation or Event Bubbling.

The event handling in "modern" browsers (which is the model most libraries hook into) works by bubbling up the document tree. What that means is if you have html document structure like this

<body>
 <div>
  <ul>
   <li>Foo</li>
   <li>Bar</li>
   <li>Baz</li>
  </ul>
 </div>
</body>

and someone clicks on Foo, the following event stuff happens

  1. The <li> receives a click event
  2. The <ul> receives a click event
  3. The <div> receives a click event
  4. The <body> receives a click event

That's what's happening to you. There's two things you can do to prevent this.

The first, as many have suggested is, if your event handler returns false, then event propagation will stop at the element that has been clicked. (this is how jQuery handles it, each library has a different approach)

$(document).ready(function() { 
 //hookup events for entire body of document
 $('body').bind('click', function(e){
  //do clicked body stuff
 });
 //hookup our divs, returning false to prevent bubbling
 $('div').bind('click', function(e){
  //do clicked div stuff
  return false;  //stop event propagation
 });  
});

A second, some would argue better, approach is to use event delegation. Event delegation, in the context of Javascript and the DOM, is where you attach a single event handler to an outer element (in this case the body), and then based on what element was originally clicked, take a particular action.

Something like this.

$(document).ready(function() { 
  //event delegate
  $('body').bind('click', function(e){
    var originalElement = e.srcElement;
    if(!originalElement){originalElement=e.originalTarget;} 
    if ($(originalElement).is('div')) {
     console.log("A div!");
    }
    else if ($(originalElement).is('div#inside')) {
     console.log("A Specific Div!");
    }
    else{
     console.log("Something else!");
    }     
  });
});

The benifits of this are two-fold. First, you can centralize all your event handling code into a single place in your code. The second (more important in my mind) is attaching a single event handler consumes much less browser memory and sets you up for better performance moving forward.

Alan Storm
The seems as the most complete answer so far. I think you could also wrap the originalElement with $() and then use fancy selectors in .is() to find out what it actually is.
che
Good point, code changed to reflect that.
Alan Storm