Álvaro González
Álvaro González

Reputation: 146490

Usage of :not() to filter out elements in event handler

I'm pretty confused by the :not() selector. In plain CSS is seems to be rather straightforward:

section { /* Overriden as expected */
  color: red;
}
input {
  color: green; /* In effect as expected */
}
:not(input) {
  color: blue; /* In effect as expected */
}
<section>
  <p>Lorem ipsum</p>
  <input value="Lorem ipsum">
</section>

However, when applied to filter the descendants of the selected elements that trigger an event I'm unable to grasp the logic:

jQuery(function($){
  $(document).on("keydown", "input", function(event){
    // This fires only for <input> as expected
    console.log("Event handler #1 on", event.target);
  });  

  $(document).on("keydown", ":not(input)", function(event){
    // This fires for *all* elements :-?
    console.log("Event handler #2 on", event.target);
    // ... even though these checks return the results that intuition suggests
    console.log('Is "input"? %s; Is ":not(input)"? %s',
      $(event.target).is("input"),
      $(event.target).is(":not(input)")
    );
  });

  $(document).on("keydown", "section :not(input)", function(event){
    // This *never* fires :-?
    console.log("Event handler #3 on", event.target);
  });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<section>
  <p>Click and type here</p>
  <input value="Click and type here">
</section>

What's the rationale behind the way :not() works here?

I'm really looking for an explanation as opposed to a fix.

Upvotes: 4

Views: 56

Answers (1)

CertainPerformance
CertainPerformance

Reputation: 370989

The issue is that the keydown event bubbles up from the input. If you use :not(input), the handler will not fire when the event was just initialized at the input element, but it will fire when the event bubbles up to the section element. You can check this by checking this inside the function, which will refer to the element to which the event has bubbled when the handler fires. (The event.target will always be the input when you're typing in the input field)

jQuery(function($){
  $(document).on("keydown", ":not(input)", function(event){
    // This fires for *all* elements :-?
    console.log("Event handler #2 on", this);
  });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<section>
  <p>Click and type here</p>
  <input value="Click and type here">
</section>

If you continue adding :nots, you'll see it bubble up all the way up to the HTML tag:

jQuery(function($){
  $(document).on("keydown", ":not(input):not(section):not(body)", function(event){
    // This fires for *all* elements :-?
    console.log("Event handler #2 on", this);
  });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<section>
  <p>Click and type here</p>
  <input value="Click and type here">
</section>

I suppose you could use :not(input):not(section):not(body):not(html), but that's a bit silly and hard to manage.

Your third handler is properly excluding the input from firing the event, but only input (and similar) elements fire keydown events - it can't be fired from a <section>, for example. It might be clearer if there's a textarea child of the section as well as the input - you'll see that the textarea triggers the handler, but the input doesn't:

$(document).on("keydown", "section :not(input)", function(event) {
  console.log("Event handler #3 on", event.target);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<section>
  <p>Click and type here</p>
  <input value="Click and type here">
  <textarea></textarea>
</section>

Upvotes: 5

Related Questions