How to use $ .on in pure JavaScript: “$ (…) .on (event, selector, function)”?

Posted on

Question :

This is in jQuery we have on , any element <a> with class test e without the foo class will fire the function when clicked, even though you create the element after the event is already added:

$("#new").click(function () {
    $('<p><a class="test" href="#">Novo: (' + (new Date) + ')</a></p>').appendTo("#container");
});

$("#container").on("click", "a.test:not(.foo)", function () {
    console.log("Funcionou!");
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script><buttonid="new">Adicionar novo</button><br>

<div id="container">
   <p><a href="#">Oi (não funciona)</a></p>
</div>

Note that in testing only the elements added later work, ie things like document.querySelector().forEach will not work, unless you use MutationObserver , but then this would still be a third behavior .

At first I thought jQuery used MutationObserver , but after a few tests I realized that actually the event is actually in document and #foobar , so how I have to use Vanilla.js I started trying recreate this, I used Element.addEventListener + Event.target , it looked something like:

var container = document.getElementById("container");
var newBtn = document.getElementById("new");

on(container, "click", "a.test:not(.foo)", function () {
    console.log("achou:", this);
});

newBtn.onclick = function () {
    var n = document.createElement("p");

    n.innerHTML = '<a class="test" href="#">Novo: (' + (new Date) + ')</a>';

    container.appendChild(n);
};

function on(target, type, selector, callback)
{
    target.addEventListener(type, function (e)
    {
         var el = e.target,
             els = document.querySelectorAll(selector);

         for (var i = 0, j = els.length; i < els.length; i++) {
             if (els[i] === el) {
                 callback.call(el, e); //Passa o elemento como this e o event como primeiro argumento
                 break;
             }
         }
    });
}
<button id="new">Adicionar novo</button><br>

<div id="container">
   <p><a href="#">Oi (não funciona)</a></p>
</div>

However this does not seem very performative, the doubt is as follows:

  • Is there any way to test a specific element with a queryselector ?

Answer :

This is delegation of events with delegate element associated with a more complex CSS selector.

$(document).on("click" % means that the event handset is tied to document . So we already have document.addEventListener .

Then we need to treat event.target to know what the event was, was in that element, or a descendant. For this we need to check the selectors. We can use .matches() that checks if a given element hits a given CSS selector, and associate it with the :not() that is already supported by modern browsers to ensure that the wrong class is not accepted.

We could do this like this:

document.addEventListener('click', function(e) {
  var correto = e.target.matches('a.test:not(.foo)');
  // ...
});

Example with tests:

document.addEventListener('click', function(e) {
  var correto = e.target.matches('a.test:not(.foo)');
  
  // o resto é só para o exemplo
  var seletor = [e.target.tagName.toLowerCase(), ...e.target.className.split(' ')].filter(Boolean).join('.');
  console.log(seletor, '|', correto ? 'encontrado!' : 'falhou...', e.target.innerHTML);

});
a {
  display: inline-block;
  margin: 5px;
  padding: 10px;
  border: 1px solid #005;
  width: 10px;
}

a:hover {
  color: #aaf;
  cursor: pointer;
}
<a class="qq-coisa">A</a>
<a class="test foo">B</a>
<a class="foo">C</a>
<a class="test">D</a>

    

An addition to Sérgio’s answer is the prefixes question, Element.matches is not supported by some older browsers, or had a different name (such as matchesSelector ).

Browsers and Element.matches

Until Chrome 33, Opera 15 and Safari 7.0 used the prefix:

.webkitMatchesSelector

Firefox up to version 33 used:

.mozMatchesSelector

Internet Explorer uses since version 9 (no version uses matches ):

.msMatchesSelector

Opera Presto from 11.5 used:

.oMatchesSelector

Alternative to the Element.matches and prefixed

Older browsers may support querySelectorAll , but may not support matches and prefixed as webkitMatchesSelector , for this the

Leave a Reply

Your email address will not be published. Required fields are marked *