instead instead - 4 months ago 14
jQuery Question

Stop eventPropagation after jQuery trigger event

Here's the situation. There is button and there is hidden div. After user clicks the button, hidden div appears and in the same time I'm adding event listener on body using

on()
to check if user clicked out of this hidden div. After that div should be hidden again and click event detached.

Everything works fine but only when user clicks the button. If I trigger click using trigger() function then
on()
function is called immediately and hidden div is not appearing.

Here is the code:



$('button').click(function(e){
e.stopPropagation();
$('div').toggleClass('active');

if($('div').hasClass('active')){
$('body').on('click.div', function(e){
console.log('body clicked')
if(! $(e.target).closest('div').length){
$('div').removeClass('active');
$('body').off('.div');
}

});

}else{
$('body').off('.div');
}
});

$('input').click(function(){
$('button').trigger('click');
});

div{
display: none;
}

div.active{
display: block;
}

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button>Click</button>
<div>This is hidden</div>
<input type="button" value="Trigger">





I found also solution how to solve this problem, but I still dont know why using
trigger()
and
stopPropagation()
does not work.

Here is fixed code with using
setTimeout()
:



$('button').click(function(e) {
e.stopPropagation();
$('div').toggleClass('active');

if ($('div').hasClass('active')) {
setTimeout(function() {
$('body').on('click.div', function(e) {
console.log('body clicked')
if (!$(e.target).closest('div').length) {
$('div').removeClass('active');
$('body').off('.div');
}

});
}, 0);

} else {
$('body').off('.div');
}
});

$('input').click(function() {
$('button').trigger('click');
});

div {
display: none;
}
div.active {
display: block;
}

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button>Click</button>
<div>This is hidden</div>
<input type="button" value="Trigger">




Answer

The reason is that the click on the input that you responded to by calling trigger propagates to body, and so it gets handled by the body handler you add. That is, the click that triggers this event handler:

$('input').click(function(){
  $('button').trigger('click');
});

...gets processed; and then of course you're sending another one later.

To fix it, just stop that click from propagating:

$('input').click(function(e){
  e.stopPropagation();
  $('button').trigger('click');
});

$('button').click(function(e){
   e.stopPropagation();
   $('div').toggleClass('active');

   if($('div').hasClass('active')){
     $('body').on('click.div', function(e){
       console.log('body clicked')
       if(! $(e.target).closest('div').length){
         $('div').removeClass('active');
         $('body').off('.div');
       }

     });

   }else{
     $('body').off('.div');
   }
});

$('input').click(function(e){
  e.stopPropagation();
  $('button').trigger('click');
});
div{
  display: none;
}

div.active{
  display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button>Click</button>
<div>This is hidden</div>
<input type="button" value="Trigger">

The reason your setTimeout version worked is that you were delaying hooking up your body click handler until after the click on the input had reached body, so it didn't get triggered by the click on the input.