Question:
The thing is, in jQuery we have on
, any <a>
element with class test
and without class foo
will trigger the function when clicked, even if 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>
<button id="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
won't work unless you use MutationObserver , but then this would still be a third behavior .
At first I thought jQuery used MutationObserver , but after some testing I realized that the event is actually in document
and #foobar
, so as I tend to use Vanilla.js I started trying to recreate this, for that I used Element.addEventListener
+ Event.target
, it was 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 to me, the question is:
- Is there any way to test a specific element with a queryselector ?
Answer:
This is event delegation with delegate element associated with a more complex CSS selector.
$(document).on("click"
means that the event listener is tied to document
. So we already have document.addEventListener
.
Then we need to handle event.target
to know that the event was on that element, or on a descendant. For that we need to check the selectors. We can use .matches()
which checks if a given element .matches()
a given CSS selector, and associate it with :not()
which is already supported by modern browsers to ensure that the wrong class is not accepted.
We could do it 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>