5 ms·
I have an example to which maybe you or someone else can suggest a better 'vanilla' solution. Would love to hear suggestions. With jQuery I'd often do $(<
by mercer 10y ago
I have an example to which maybe you or someone else can suggest a better 'vanilla' solution. Would love to hear suggestions.
With jQuery I'd often do
$(<root-element>).on('click', <target element>, callback);
crucially, because events bubble up, even a click on a child element of <target element> will register as a click on the target element.
in plain js it doesn't seem to work that way. I'd do <root-elem>.addEventListener(), but I'd run into the problem that the event.target would point at the exact element that was clicked on instead of the parent element that I wanted to register clicks on.
Given the structure ".item .inner .title", I can't just check if event.target.classList contains 'item', because it might only contain 'title' or 'inner'.
event.path to the rescue! I can simply check if any of the parent nodes do contain the 'item' class. Except event.path doesn't work in safari mobile, so it needs a polyfill. Plus on every event handler I have to go up event.path to check.
Obviously it's not that hard to write a helper function that basically emulates .on(), but I can't help but feel that I'm maybe missing some simpler, plain solution. Any suggestions?
- dham 10y agoThis is what I use in my vanilla js projects for event delegation. function delegate(el, evt, sel, handler) { el.addEventListener(evt, function (event) { let t = event.target while (t && t !== this) { if (t.matches(sel)) { handler.call(t, event) } t = t.parentNode } }) } delegate(document, 'click', '[data-behavior="open"]', function (event) { event.preventDefault() }) You'll need a matches polyfill if supporting browsers that don't support el.matches
- losvedir 10y ago> in plain js it doesn't seem to work that way. I'd do <root-elem>.addEventListener(), but I'd run into the problem that the event.target would point at the exact element that was clicked on instead of the parent element that I wanted to register clicks on. Unless I misunderstand, I think you might be looking for "event.currentTarget". event.target is the element that was clicked on, event.currentTarget is the element that has the event listener attached.
- mercer 10y agoYeah, but that would then either be 'document' or the 'root' of my widget/component, no? Not the 'item' which is somewhere in between the root and the target. $().on() is very convenient in that way.
- losvedir 10y agoI see, I missed the "<target element>" part of the call. Well, why not just attach the event listener to that, then? `querySelector` / `querySelectorAll` can be called on any old element, not just document. <root-element> .querySelector(<target element>) .addEventListener('click', function(event){ // event.currentTarget refers to // <target element> no matter which of its // children were clicked }); Admittedly, this doesn't work as well for multiple target elements. I use a forEach polyfill which could loop through them, but that could create a lot of event listeners when possibly you'd only need one higher up. Depends how many there are, I suppose.
- mercer 10y agoThe problem with attaching the listener to the 'actual' target is that it goes wrong when your markup is dynamic and so the target element is not (always) there the moment you create the listener. This is why one of the early lessons you learn as a jQuery user is to use $().on() instead of something like $().click(); Plus, while I don't suppose it's a real performance issue much of the time, adding an event listener to each of, say, a few hundred items in a list is probably rather inefficient compared to adding it to the root element of the list.
- TekMol 10y agoNot completely sure what you mean. Could you make a minimal example on jsfiddle? Then I can make the vanilla counterpart and we can compare.
- mercer 10y agoSorry, no time right now for that. I'll try to explain more clearly. There's no problem if I can bind directly to the element. However, that's often not possible. Imagine a <Carousel> element that is initialized, event listeners and all, but only receives data (<Item> elements) at a later time. In jQuery I can do something like this: $(<Carousel>).on('click', <Item>, function(e) { // using this 'this' gives you the <Item> element. Always. Even // if you click on a child element of <Item>. And it works if // you add or remove items. Also it doesn't add a listener // to *every* single item, which matters if you've got a whole // bunch of them! }); The vanilla equivalent is often presented to be as simple as: <Carousel>.addEventListener('click', function(e) { // e.target is the actual element you clicked. So now we need to // figure out if this element is inside an <Item>, and perhaps // also *which* <Item>. There's various approaches, and it's // not rocket science, but it's all extra work for something that // seems like a common scenario. });