282

I have a DIV with a classed foobar, and a few DIVs inside that DIV that are unclassed, but I suppose they are inheriting the foobar class:

$('.foobar').on('click', function() { /*...do stuff...*/ });

I want that to fire off only when clicking somewhere in the DIV but not on its children DIVs.

Saurav Rastogi
  • 9,575
  • 3
  • 29
  • 41
CaptSaltyJack
  • 15,283
  • 17
  • 70
  • 99

12 Answers12

518

If the e.target is the same element as this, you've not clicked on a descendant.

$('.foobar').on('click', function(e) {
  if (e.target !== this)
    return;
  
  alert( 'clicked the foobar' );
});
.foobar {
  padding: 20px; background: yellow;
}
span {
  background: blue; color: white; padding: 8px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class='foobar'> .foobar (alert) 
  <span>child (no alert)</span>
</div>
  • That answer explains [this](http://stackoverflow.com/questions/9146163/jquery-closest-with-attribute-filter#comment11499568_9146163) comment – gdoron Feb 07 '12 at 20:44
  • @gdoron: Adam is being too kind. :) –  Feb 07 '12 at 20:47
  • can you please answer my [question](http://stackoverflow.com/q/9193293/601179)... =) – gdoron Feb 08 '12 at 15:16
  • just removed an extra = in the if condition. – vicky Jul 09 '15 at 18:15
  • 3
    Hi @vicky. Just FYI, JavaScript has two different kinds of value equality comparison. The `===` or `!==` uses the *"Strict Equality Comparison Algorithm"*, while the `==` and `!=` uses the *"Abstract Equality Comparison Algorithm"*, which is type coercive. The general wisdom is to always use *strict* comparison unless there's specific need for the coercive type *(since its rules are a little complex)*. In this case, because objects are being compared, it really doesn't make a difference, but you'll find that most people will still stick with the *strict* comparison. –  Jul 09 '15 at 18:26
  • ohhh really. I didnt knew this. Sorry it was a mistake then. – vicky Jul 10 '15 at 21:34
  • 1
    @vicky: That's alright. It's still correct. Just letting you JS has the different operators. ;-) –  Jul 11 '15 at 00:23
  • your code was having a problem you should write alert function first. Update [fiddle](http://jsfiddle.net/eaVzx/550/). :) – Gaurav Aggarwal Dec 03 '15 at 07:33
  • 1
    @GauravAggarwal: The purpose of having the `if` statement first is to make it so that the code below it does not run when clicking on children of the `.foobar` element. If we put the alert on top, it will always run. So basically, you should see the alert when clicking on the outer yellow area, but not on the blue child. –  Dec 03 '15 at 15:22
  • 3
    This is a good answer because it can also work with Vanilla JS addEventListener and is not jQuery specific. – Michael Giovanni Pumo Aug 12 '16 at 12:15
  • In ReactJs, you would attach a reference to the parent div like
    this.ref_name = div}>
    , and you would compare e.target with this.ref_name instead of with this
    – claireablani Sep 09 '17 at 00:02
  • this accepted answer is not always correct due to the context of `this`... `this` could be pointing to something completely different than what you are expecting... Jens Törnell's answer below should be the correct answer. – eballeste Jul 15 '19 at 19:30
  • 1
    how to do this on vanilla js? – Irvan Hilmi Dec 19 '20 at 02:10
123

I did not get the accepted answer to work, but this seems to do the trick, at least in vanilla JS.

if(e.target !== e.currentTarget) return;
Jens Törnell
  • 23,180
  • 45
  • 124
  • 206
  • 8
    e.currentTarget is more accurate than referencing `this` because the object that `this` is pointing to can change depending on the scope and context from where it is called – eballeste Jul 15 '19 at 19:38
  • 5
    `event.currentTarget` looks to be the cleanest method here. – Mark Carpenter Jr Aug 17 '20 at 01:27
  • 4
    note that you can also do `if (e.target === e.currentTarget) { ...your code }` why this works: e.target is the element that triggered the event (e.g., the user clicked on) e.currentTarget is the element that the event listener is attached to. so when you compare these two the element which the event is attached is same as the one was clicked. – Guilherme Samuel Feb 05 '21 at 20:25
  • Thanks the solution works perfectly for my problem. I observed is that when you log "e" and open the logged object currentTarget is null but when you log "e.currentTarget" it logs the element correctly. I am confused why? – Akash Kumar Seth Jul 09 '21 at 09:31
  • Got my answer "Because the e.currentTarget property of the event object changes while propagating". – Akash Kumar Seth Jul 09 '21 at 10:01
  • 1
    best answer for sure – Cybernetic Mar 01 '22 at 03:18
110

There's another way that works if you don't mind only targeting newer browsers. Just add the CSS

pointer-events: none;

to any children of the div you want to capture the click. Here's the support tables

http://caniuse.com/#feat=pointer-events

hobberwickey
  • 6,118
  • 4
  • 28
  • 29
  • 33
    Know that I should avoid writing 'thanks' comments, but I can kiss you foot for this :-) – Simona Adriani Mar 29 '17 at 09:24
  • @SimonaAdriani huh? – n3wb Jul 23 '18 at 23:21
  • Thanks, I did the same way, but I'm struggling to write unit test case around it. How can I make sure `click` programatic click event trigger should be blocked? – Pankaj Parkar Dec 16 '18 at 06:44
  • This is not correct. You may still want to allow events on the child element, just not the parent element. For example, the parent might have a mouseover event, which you don't want on the child element that is an input, but obviously you want to use the input. – Cybernetic Mar 01 '22 at 03:15
  • @Cybernetic - In that case you can just handle the click (or focus) event on the parent and then focus the input with `input.focus()`, but in any case there's multiple solutions to this problem and which one someone uses will be based on their use case anyway. – hobberwickey Mar 01 '22 at 17:21
37

You can use bubbling in your favor:

$('.foobar').on('click', function(e) {
    // do your thing.
}).on('click', 'div', function(e) {
    // clicked on descendant div
    e.stopPropagation();
});
ori
  • 7,817
  • 1
  • 27
  • 31
23
//bind `click` event handler to the `.foobar` element(s) to do work,
//then find the children of all the `.foobar` element(s)
//and bind a `click` event handler to them that stops the propagation of the event
$('.foobar').on('click', function () { ... }).children().on('click', function (event) {
    event.stopPropagation();
    //you can also use `return false;` which is the same as `event.preventDefault()` and `event.stopPropagation()` all in one (in a jQuery event handler)
});

This will stop the propagation (bubbling) of the click event on any of the children element(s) of the .foobar element(s) so the event won't reach the .foobar element(s) to fire their event handler(s).

Here is a demo: http://jsfiddle.net/bQQJP/

Jasper
  • 75,717
  • 14
  • 151
  • 146
6

I had the same problem and came up with this solution (based on the other answers)

 $( ".newsletter_background" ).click(function(e) {
    if (e.target == this) {
        $(".newsletter_background").hide();
    } 
});

Basically it says if the target is the div then run the code otherwise do nothing (don't hide it)

Wonx2150
  • 149
  • 2
  • 12
5
$(".advanced ul li").live('click',function(e){
    if(e.target != this) return;
    //code
    // this code will execute only when you click to li and not to a child
})
Michalis
  • 6,686
  • 13
  • 52
  • 78
3

You can use event.currentTarget. It will do click event only elemnt who got event.

target = e => {
    console.log(e.currentTarget);
  };
<ul onClick={target} className="folder">
      <li>
        <p>
          <i className="fas fa-folder" />
        </p>
      </li>
    </ul>
2

If you can't use pointer-events: none; and are targeting modern browsers you can use composedPath to detect a direct click on the object like so:

element.addEventListener("click", function (ev) {
    if (ev.composedPath()[0] === this) {
        // your code here ...
    }
})

You can read more about composedPath here: https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath

Xenxier
  • 431
  • 4
  • 7
1

My case is similar but this is occasion when you have few foobar-s, and you want to close only one - per one click:

Find parent case

$(".foobar-close-button-class").on("click", function () {
    $(this).parents('.foobar').fadeOut( 100 );
    // 'this' - means that you finding some parent class from '.foobar-close-button-class'
    // '.parents' -means that you finding parent class with name '.foobar'
});

Find child case

$(".foobar-close-button-class").on("click", function () {
    $(this).child('.foobar-close-button-child-class').fadeOut( 100 );
    // 'this' - means that you finding some child class from '.foobar-close-button-class'
    // '.child' -means that you finding child class with name '.foobar-close-button-child-class'
});
Wukadin
  • 11
  • 2
1

When you define an event, the event has a property this. This property represents the DOMElement to which the event was assigned.To check the element in which the event was fired, use e.target.

Since events are inherited in the children of an element, check if the target

function doSomething(event) {
  if (this == event.target){
    // do something
  }
}
0

// if its li get value 
document.getElementById('li').addEventListener("click", function(e) {
                if (e.target == this) {
                    UodateNote(e.target.id);
                }
                })
                
                
                function UodateNote(e) {

    let nt_id = document.createElement("div");
    // append container to duc.
    document.body.appendChild(nt_id);
    nt_id.id = "hi";
    // get conatiner value . 
    nt_id.innerHTML = e;
    // body...
    console.log(e);

}
li{
 cursor: pointer;
    font-weight: bold;
  font-size: 20px;
    position: relative;
    width: 380px;
    height: 80px;
    background-color: silver;
    justify-content: center;
    align-items: center;
    text-align: center;
    margin-top: 0.5cm;
    border: 2px solid purple;
    border-radius: 12%;}
    
    p{
     cursor: text;
  font-size: 16px;
   font-weight: normal;
    display: block;
    max-width: 370px;
    max-height: 40px;
    overflow-x: hidden;}
<li id="li"><p>hi</p></li>
Omar bakhsh
  • 896
  • 11
  • 18