circlecube
circlecube

Reputation: 704

jQuery event listener fires before selector applied?

I'm stuck on some event listeners. When one element is clicked I want to create another listener for clicks. For some reason they are both firing. Here's the code and a jsbin: http://jsbin.com/uHIyiGi/2/edit HTML

<div class="wrap">
 <button>Click me</button>
</div>

JS

$('button').on('click', function(event){
  $('body').append('<p>button clicked</p>');
  $('.wrap').addClass('clicked');

  $('body').on('click', '.clicked', function(e){
    $('body').append('<p>.clicked clicked</p>');
    $('.wrap').removeClass('clicked');
    $('body').off('click', '.clicked');
  });
});

From what I understand about event listeners, the second one should not fire until the '.clicked' element has a complete click event (a mouse down and mouse up), but it fires on the first click, meaning the event is fired even though the element doesn't have the trigger class until after the event. I'm seeing the event listener successfully removed each time, but it's called before I think it should be called. So, I'm really confused now... any suggestions? I'm hoping I have a face palm in my near future.

Upvotes: 2

Views: 1339

Answers (1)

Kevin B
Kevin B

Reputation: 95022

Events bubble. When you click on the button, you're adding an event to the body. Once the event handler for the button is complete (meaning, the wrap now has a clicked class), it then continues to bubble up the DOM until it reaches the body. Since the body now has a new click event, said event also gets triggered. jQuery then looks within the body for an element matching the .clicked selector, calling the handler on any that it finds. To stop this, you can either delay binding the event to the body with a setTimeout, or stop Propagation on the button's click event so that it won't bubble up to the body.

$('button').on('click', function(event){
    event.stopPropagation();
    ...

Timeline:

  1. mousedown event is triggered, followed by a mouseup event both on the button
  2. click event handler is triggered on button
  3. clicked class is added to .wrap div
  4. click event is bound to the body, delegated to elements matching .clicked
  5. event bubbles up through the dom to the body
  6. click event handler is triggered on the body
  7. jQuery's click event handler looks for any elements that are descendants of the current element (the body) that match the .clicked selector
  8. it finds the .wrap div that has a clicked class and calls the event handler on that element.

Upvotes: 5

Related Questions