DBS DBS - 7 months ago 60
Javascript Question

Why does an event fire on a parent element before it's child when bound with a descendent selector?

I was tripping over an odd bug in my code where an event on a parent element appeared to fire before the event on it's child, meaning that my

e.stopPropagation()
had no effect.

Demo:



$(document).ready(function() {

// Binding directly to the elements
$(".red1").on("click", function(e) {
alert("Clicked red1");
});
$(".green1").on("click", function(e) {
alert("Clicked green1");
e.stopPropagation();
});

// Binding the child from a descendant selector
$(".red2").on("click", function(e) {
alert("Clicked red2");
});
$("body").on("click", ".green2", function(e) {
alert("Clicked green2");
e.stopPropagation();
});

});

.red1,
.red2 {
display: inline-block;
width: 200px;
height: 200px;
background-color: #800;
}

.green1,
.green2 {
display: inline-block;
width: 100px;
height: 100px;
background-color: #080;
}

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div class="red1">
<div class="green1"></div>
</div>

<div class="red2">
<div class="green2"></div>
</div>






  • Clicking the green square on the left works as expected and shows a single alert.

  • Clicking the green square on the right appears to fire the parent's event, followed by the child's.



I assume that this is due to a misunderstanding of how the binding works on my part, but I can't seem to get my head around why they're occurring in this order.

Can anyone explain why this is happening?

Answer Source

The issue is because you're using a delegated event handler.

This means that for the event to fire it has to bubble up to the designated parent element (body in your case). As the event passes through .red2 the static handler you assigned to that element fires. Then delegated event handler checks to see if the event originator was .green2. If it was then delegated event handler is executed. This is why the parent handler fires first.

To avoid this behaviour, you can either avoid delegated event handlers, which isn't always possible as they are incredibly useful, or place all events on the parent elements, and check the originator manually, like this:

$(".red2").on("click", function(e) {
  if ($(e.target).is('.green2')) {
    alert("Clicked green2");
    e.stopPropagation();
  } else {
    alert("Clicked red2");
  }
});
.red1,
.red2 {
  display: inline-block;
  width: 200px;
  height: 200px;
  background-color: #800;
}

.green1,
.green2 {
  display: inline-block;
  width: 100px;
  height: 100px;
  background-color: #080;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div class="red2">
  <div class="green2"></div>
</div>

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download