-
Notifications
You must be signed in to change notification settings - Fork 294
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Event delegation via EventListenerOptions #215
Comments
See https://gist.github.com/annevk/4475457 where I had the idea of adding an The combination of I was thinking (Thanks for raising this!) |
I dig it.
Yeah, a rough implement usually looks something like: document.addEventListener('click', function(event) {
var delegateTarget = this
var currentTarget = event.target.closest('.foo')
if (!currentTarget) {
return
}
// ...
}) |
Interesting. That snippet seems to set Would love your input on the details and also from @wycats if he can find the time. |
In general I think we should anticipate the callback this value becoming much less useful once arrow functions arrive. So exposing the relevant information through From what I remember using event delegation back in my jQuery and Backbone days it was always confusing which of target or currentTarget was correct for my use case. |
Maybe if the API explicitly couples them it would be clearer: obj.addEventListener(type, (e) => {
e.target // the actual target
e.currentTarget // obj
e.delegateTarget // .baz
}, { delegate: ".baz", ignoreBubbles: true }) If Another thing to consider is whether |
Not saying I prefer it, but these are the semantics jQuery uses. I'd say there logic is that they wanted Similarly, if you direct bind the handler, the matched target is your current target. document.querySelector('.foo').addEventListener('click', function() {
this // is a .foo
})
obj.addEventListener('click', function() {
this // is a .foo
}, { matches: ".foo" }) I've pretty much never used
https://api.jquery.com/category/events/event-object/ If we want to keep
👍 My "delegated xpath events" lib is going to be so popular someday! |
@jaubourg @paulirish any input on whether we should attempt to copy the jQuery conventions and adjust what |
@scottgonzalez any thoughts? |
Agree with @domenic . Having a special logic for |
I'd like to argue for formalizing jQuery semantics as follows:
Doing so would preserve two important properties of the current model: equivalence of You can get a feel for things at https://jsfiddle.net/yrk2pwky/, which also highlights the interesting edge case of a single event matched by the same delegate event listener at multiple points along its propagation path (for which I'm not married to jQuery behavior). ¹ "the object whose event listener’s callback is currently being invoked" in the current (non-delegating) model |
@annevk Thanks for the ping. I had started preparing a response, but decided to pull in someone else with more experience in the actual implementation of jQuery's event system. You can take @gibson042's response in place of what I was going to write :-) |
I agree with @gibson042 as far as the targets go. It would introduce more confusion to redefine everything. The rough implementation that @josh mentions above is much simpler than what is happening in jQuery though. That can only execute the handler once in the subtree and that particular code as-is could execute on an element above the delegation point. The jQuery implementation bubbles the event from the target element up to but not including the delegate point element and executes the handler for any matches it finds; there can be multiple matches along the path. For each match, the handler executes and has the opportunity to call I don't know whether jQuery's more general implementation is required to handle a lot of use cases or not. |
Thank you for the input, this is much appreciated. Let's see if I have understood it all with an example. Tree: Now I register a delegate listener on This is a little more involved than using selectors to filter the target. Would be interesting to hear what @dominiccooney and @smaug---- et all think of that. It also gives us two choices as to how they integrate with the existing model:
Neither seems really attractive to me. |
Yes, and I am strongly in favor of option 1 (process all delegate callbacks before advancing |
Option 1 (for bubble and capture) would seem a better fit to the user's existing mental model about how events behave, option 2 is what jQuery was stuck with given the constraints of the current DOM API. The main concern I'd have is the additional cost and complexity. For GitHub the amount of extra overhead from delegation via jQuery was too much so they created their own delegation code. However, it only works for delegation done from |
Interesting, I suppose we should ask @dgraham for feedback too then. It seems the main performance benefit listed in that repository is no longer having to create synthetic event wrappers, which is not directly related to delegation. But yeah, for large trees having to check all ancestor nodes in an event's path while iterating through an event's path to find delegate listeners is either going to be slow or require complicated data structures. |
The main performance issue is that iteratively testing selectors is linear with the number of installed delegated handlers. So yeah, you'd want to use a complicated data structure to make this more efficient. Luckily most of the hard work has already been done here by browser engines as they need theses structures for CSS rule set matching. Blink, Gecko and WebKit all use some sort of variation on a hash map or bloom filter for fast path testing. @github uses this selector set implementation for its batch selector testing.
There's nothing
I'd love to have this implementation. This would allow delegated handlers to use <a>
<b>
<c> a.addEventListener('x', () => { log('a') })
b.addEventListener('x', () => { log('b') })
a.addEventListener('x', (e) => { log('c'); e.stopPropagation() }, {delegate: 'c'})
|
I thinks this has been clarified already but some comments talk about using As a front-end dev if I have |
No, see my comment above. The user's own event handler often uses |
It's on my goals for this quarter (so, next week) to prototype a spec and tests for this. Unlike the other EventTarget stuff I've been taking on, I'm not sure I understand all the subtleties here fully, so help would be appreciated. Let me try to summarize what I think is the current best plan:
Does this seem reasonable? |
I certainly don't want to prevent you from prototyping this, but I don't understand what is the added value of the proposed event bubbling sequence. I would love if someone could explain to me in which cases is the proposed sequence is useful! I am asking because EventTarget is a generic interface implemented by things for which css selectors do not mean anything, and if the proposed event bubbling is not something that can be proven to be an advantage, I have a tough time grasping why |
@FremyCompany It’s better because the proposed matching is not on It’s a great question though, because it shows that this option should not be called Tossing a coin in the bikeshed machine:
I vote "anything but document.body // event.delegateTarget
.addEventListener('x', event => { … }, {
delegateSelector: '.foo' // event.currentTarget
}) Possible solutions when keeping the "delegation" root:
With the "for" root, you can also do:
My faves might be
This seems fine. It follows the precedent set by jQuery but also adds the ability to retrieve the element the event listener is attached to (delegateTarget). One could argue that the jQuery way was "bad" it would be cleaner to not redefine |
Ok... I think I understand the proposal better now. Do I?
would be similar in spirit to
Is that right? |
Looping in this thread on WICG about event listeners applied by selector. |
Would this allow for multiple firings of events that can normally wouldn't fire again for the parent? ( For example would this fire an event for every delegated child that is container.addEventListener('mouseenter', event => {
// do stuff
}, { matches: '.hoverable' }) Or would it still behave like current and only fire an event when entering the element the event listener is attached to (assuming it crosses a child that matches)? e.g. Roughly similar to the current code: container.addEventListener('mouseenter', event => {
// This is only fired on entering my container so turns out to be
// pretty useless for detecting hover over child nodes
if (event.target.matches('.hoverable')) {
// do stuff
}
}) SECOND EDIT: As it turns out I was getting doubly confused with both |
Event delegation is a common feature of most framework's event handling libraries. It'd be rad if it was standard in DOM events.
Things to figure out:
event.target
andevent.currentTarget
should be behave in this case. And if there should be a thirdevent.delegateTarget
.bubbles: false
https://twitter.com/annevk/status/719534820569804800CC: @annevk
References: #207 #208
The text was updated successfully, but these errors were encountered: