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

18

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 ?
asked by anonymous 06.06.2017 / 21:42

3 answers

21

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>
    
06.06.2017 / 21:50
8

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

08.06.2017 / 20:42
0

This version of the answer works with the element you created later. See if the behavior is closer than you need.

Note that in the jQuery on, the event goes up from the clicked target to the element where the event was attached (in the example, document), and I tried to simulate it here too, where I attached an event to when I clicked on some part in the parent div of the links tests that part.

Finally, putting together the ideas of colleagues and understanding the question better, we can have something like this:

<html>
<head>
<title>Sem necessidade para jQuery</title>

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

a:hover {
  color: #aaf;
  cursor: pointer;
}
</style>
</head>

<body>

<div class='links'>
  <a class="qq-coisa">A</a>
  <a class="test foo">B</a>
  <a class="foo">C</a>
  <a class="test">D</a>
</div>

</body>

<script>
Node.prototype.meuOn = function(tipo, seletor, callback) {
  this.addEventListener(tipo, function(evento) {
    var el = evento.target;
    do {
      if (el.matches(seletor)) {
        callback(evento);
        break;
      }
    } while ((el = el.parentNode) != this);
  });
}

document.meuOn('click', 'a.qq-coisa', function(evento) { console.log('qualquer coisa'); console.log(evento); });
document.meuOn('click', 'a.foo', function() { console.log('foo'); });
document.meuOn('click', 'a.test:not(.foo)', function() { console.log('meuOn correto'); });
document.meuOn('click', 'div.links', function() { console.log('clicou no div?'); });

var newLink = document.createElement('a');
newLink.classList.add('qq-coisa');
newLink.innerHTML = 'E?';
document.body.appendChild(newLink);


</script>

</html>

Useful links

06.06.2017 / 23:01