Better event listeners

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
56 messages Options
123
Reply | Threaded
Open this post in threaded view
|

Better event listeners

Anne van Kesteren-4
We discussed this a long time ago. Lets try again. I think ideally we
introduce something like http://api.jquery.com/on/ Lets not focus on
the API for now, but on what we want to accomplish.

Type and callback are the basics. Selector-based filtering of
event.currentTarget should be there, but optional. I'm not sure about
the data feature, Yehuda?

Should we make all events bubble for the purposes of this new
registration mechanism? I actually thought Jonas said jQuery did that
at some point, but the jQuery documentation does not suggest it does.

Should we have event-data-based filtering? E.g. developers could
register to only get key events for the WASD keys. Optimization for
developers could be that they get to have one callback per key,
optimization for browsers and speed of the application would be that
less events need to be processed ideally leading to a better user
experience.

It seems capture-listeners are not needed. At least for now.


--
http://annevankesteren.nl/

Reply | Threaded
Open this post in threaded view
|

Re: Better event listeners

Olli Pettay
On 01/05/2013 02:26 PM, Anne van Kesteren wrote:
> We discussed this a long time ago. Lets try again. I think ideally we
> introduce something like http://api.jquery.com/on/ Lets not focus on
> the API for now, but on what we want to accomplish.
>
> Type and callback are the basics. Selector-based filtering of
> event.currentTarget should be there,
Why (I'm not against it)? Also, how should it work? Should the selector matching run when
event target chain is created or when the event is actually handled.
If the DOM is modified while event is being dispatched, and some parts of the event target chain
isn't anymore in the tree, selector based filtering may cause unexpected results.


  but optional. I'm not sure about
> the data feature, Yehuda?
>
> Should we make all events bubble for the purposes of this new
> registration mechanism?
Why?

> I actually thought Jonas said jQuery did that
> at some point, but the jQuery documentation does not suggest it does.
>
> Should we have event-data-based filtering? E.g. developers could
> register to only get key events for the WASD keys. Optimization for
> developers could be that they get to have one callback per key,
the filtering should be something generic which applies to all the event interface types, even
for those not yet defined.
(Can be tricky to implement and might slow down event handling in certain cases)

> optimization for browsers and speed of the application would be that
> less events need to be processed ideally leading to a better user
> experience.
For things like key events this really doesn't matter much.
For mousemoves and touch events it might.

>
> It seems capture-listeners are not needed. At least for now.
Er, what?





-Olli

Reply | Threaded
Open this post in threaded view
|

Re: Better event listeners

Marcos Caceres-4
In reply to this post by Anne van Kesteren-4


On Saturday, 5 January 2013 at 12:26, Anne van Kesteren wrote:

> We discussed this a long time ago.

One thread (don't know if there were others):
http://lists.w3.org/Archives/Public/www-dom/2012JanMar/0102.html 
> Lets try again. I think ideally we
> introduce something like http://api.jquery.com/on/ Lets not focus on
> the API for now, but on what we want to accomplish.
>
> Type and callback are the basics.
agree.
> Selector-based filtering of
> event.currentTarget should be there, but optional.

agree.
> I'm not sure about
> the data feature, Yehuda?

This is very useful as only custom events provides "details". Sometimes you need more context (i.e., data) than just what is provided by DOM Event or any UI events that are layered on top.

> Should we make all events bubble for the purposes of this new
> registration mechanism? I actually thought Jonas said jQuery did that
> at some point, but the jQuery documentation does not suggest it does.
>
> Should we have event-data-based filtering? E.g. developers could
> register to only get key events for the WASD keys. Optimization for
> developers could be that they get to have one callback per key,
> optimization for browsers and speed of the application would be that
> less events need to be processed ideally leading to a better user
> experience.

That would be nice (though this might need to happen at a higher level). Trying to resist thinking about what the API might look like :)
> It seems capture-listeners are not needed. At least for now.
>

I've run into situations where I've needed capture, but I cant' remember the specific situation right now.

--
Marcos Caceres
http://datadriven.com.au




Reply | Threaded
Open this post in threaded view
|

Re: Better event listeners

Glenn Maynard
In reply to this post by Anne van Kesteren-4
On Sat, Jan 5, 2013 at 6:26 AM, Anne van Kesteren <[hidden email]> wrote:
We discussed this a long time ago. Lets try again. I think ideally we
introduce something like http://api.jquery.com/on/ Lets not focus on
the API for now, but on what we want to accomplish.

Type and callback are the basics. Selector-based filtering of
event.currentTarget should be there, but optional. I'm not sure about
the data feature, Yehuda?

Event delegation wants filtering on event.target, not currentTarget.  Do you mean that it should support filtering on currentTarget in addition to target (in which case: what for?)
 
Should we make all events bubble for the purposes of this new
registration mechanism? I actually thought Jonas said jQuery did that
at some point, but the jQuery documentation does not suggest it does.

That depends on the API.  I don't think we necessarily need a new method; we can override addEventListener straightforwardly, when the third parameter is a dictionary instead of a boolean:

element.addEventListener("click", handler, {
    // Only fire the handler if event.target matches this selector at dispatch time.
    filter: ".click",

    // If this is a non-capturing listener, fire this event during the bubble phase even if the event's bubbles
    // flag is false.
    alwaysBubble: true,

    // If true, this is a capturing listener.
    capture: true,
});

This allows overriding the annoying bubbles flag, but without it being a subtle, implicit difference between APIs.  It does mean event delegation takes a little more typing, but it's not bad:

container.addEventListener("focus", handler, { filter: "input", alwaysBubble: true });

The "handler = container.addEventListener(); handler.stop();" pattern could probably be supported here, too, unless for some reason making addEventListener return a value actually has web compat problems.

Should we have event-data-based filtering? E.g. developers could
register to only get key events for the WASD keys. Optimization for
developers could be that they get to have one callback per key,
optimization for browsers and speed of the application would be that
less events need to be processed ideally leading to a better user
experience.

This is something I hear a lot but have never seen cause any real-world issue.  Event dispatch is fast, and keypresses are slow.

It seems capture-listeners are not needed. At least for now.

Again, they're absolutely needed; it's a basic, critical feature of the event model.  It's also just a flag on the handler, so I don't know why this is being suggested.


On Sat, Jan 5, 2013 at 6:52 AM, Olli Pettay <[hidden email]> wrote:
Should we make all events bubble for the purposes of this new
registration mechanism?
Why?

You always want events to bubble for event delegation.

Also, how should it work? Should the selector matching run when
event target chain is created or when the event is actually handled.
If the DOM is modified while event is being dispatched, and some parts of the event target chain
isn't anymore in the tree, selector based filtering may cause unexpected results.

I'd perform the check immediately before invoking the event listener.  It guarantees that the filter always matches at the time the listener is actually invoked.  Precalculating the filter would also be weird for the case where event listeners are added during event dispatch, after the precalculation has already happened.

--
Glenn Maynard

Reply | Threaded
Open this post in threaded view
|

Re: Better event listeners

Boris Zbarsky
On 1/5/13 2:30 PM, Glenn Maynard wrote:
> we can override addEventListener straightforwardly, when the
> third parameter is a dictionary instead of a boolean

You'll have to use a union with switching on the passed-in type in the
prose (because people made the boolean optional, and dictionaries are
always optional, so using overloads here would make 2-argument calls
ambiguous).

But I think the union _might_ work.  I haven't checked carefully.

-Boris

Reply | Threaded
Open this post in threaded view
|

Re: Better event listeners

Glenn Maynard
On Sat, Jan 5, 2013 at 6:42 PM, Boris Zbarsky <[hidden email]> wrote:
You'll have to use a union with switching on the passed-in type in the prose (because people made the boolean optional, and dictionaries are always optional, so using overloads here would make 2-argument calls ambiguous).

You could make the boolean non-optional to get the same effect, since the dictionary overload's defaults should be identical to addEventListener(e, h, false).

--
Glenn Maynard

Reply | Threaded
Open this post in threaded view
|

Re: Better event listeners

Olli Pettay
In reply to this post by Glenn Maynard
On 01/05/2013 09:30 PM, Glenn Maynard wrote:
> On Sat, Jan 5, 2013 at 6:52 AM, Olli Pettay <[hidden email] <mailto:[hidden email]>> wrote:
>
>         Should we make all events bubble for the purposes of this new
>         registration mechanism?
>
>     Why?
>
>
> You always want events to bubble for event delegation.



Really? How would it work in a sane way with mouseenter/leave which behavior
is largely about non-bubbling.

Or perhaps you don't meant normal bubbling but you mean that if there is
this kind of special event listener in an ancestor, such listener should be
called even is the event doesn't bubble.
(Such would sound very much like what happens with XBL1+non-bubbling events)





-Olli

Reply | Threaded
Open this post in threaded view
|

Re: Better event listeners

Olli Pettay
In reply to this post by Glenn Maynard
On 01/05/2013 09:30 PM, Glenn Maynard wrote:
> On Sat, Jan 5, 2013 at 6:52 AM, Olli Pettay <[hidden email] <mailto:[hidden email]>> wrote:
>
>         Should we make all events bubble for the purposes of this new
>         registration mechanism?
>
>     Why?
>
>
> You always want events to bubble for event delegation.

Or just use capturing event listeners.


Reply | Threaded
Open this post in threaded view
|

Re: Better event listeners

Jake Verbaten
In reply to this post by Anne van Kesteren-4
As a web developer if I were to design an on function with some kind of delegation support I would have an API like: https://gist.github.com/4463430

The exact API doesn't matter.

Emphasis being on that `.on(...)` returns some kind of token that can be used to remove the listener so we don't have to keep a reference to the listener ourself somewhere.

The other emphasis is that delegation filtering supports arbitrary functions which allows people to delegate  whatever they want instead of a limited string based DSL

I also currently use [ever][1] partially for cross-browser normalization but mostly because I prefer writing `.on(...)` and it matches with the other EventEmitter apis I use in the rest of my code. So there's definitely value to having an API name that matches other conventions (and is terser).

Reply | Threaded
Open this post in threaded view
|

Re: Better event listeners

Glenn Maynard
In reply to this post by Olli Pettay
On Sun, Jan 6, 2013 at 7:01 AM, Olli Pettay <[hidden email]> wrote:
Really? How would it work in a sane way with mouseenter/leave which behavior
is largely about non-bubbling.

I'm not sure I understand the question.  For event delegation, you want mouseenter to bubble, just like any other event.

Note that event not bubbling is only a convenience.  It's trivial to get the same effect in an event listener without it: if(e.eventPhase != Event.AT_TARGET) return;

Or perhaps you don't meant normal bubbling but you mean that if there is
this kind of special event listener in an ancestor, such listener should be
called even is the event doesn't bubble.

Firing event listeners on ancestors, after calling it on the target, is exactly what bubbling is.

All this would mean (regardless of whether via an addEventListener flag, as I suggested, or implicitly with another method, as Anne suggested), is that all events would bubble, but event listeners would only be fired during the bubble phase if either 1: the event's bubbles flag is set, or 2: the event listener's always-bubbles flag is set.  The result would be identical for existing code (as it needs to be, of course), since listeners with the current APIs would always have their always-bubbles flags unset.


On Sun, Jan 6, 2013 at 7:02 AM, Olli Pettay <[hidden email]> wrote:
Or just use capturing event listeners.

I think this can only be recommended as a solution if it's correct to *always* use capture for the delegation pattern.  If you need to carefully use capture for some cases and not for others, for the same basic pattern, it'd be better to solve the bubbles flag problem itself.

(I don't feel too strongly about this, but if we're going to do it at all, I think extending addEventListener to take a dictionary and make this an option is better than adding a new entry point with subtly-different behavior.  It's too subtle a difference, so it'll probably confuse and bite people.)


On Mon, Jan 7, 2013 at 11:12 AM, Benoit Marchant <[hidden email]> wrote:
- How about a better way when your handlers are objects and not functions? The big problem with functions is removing the listeners, it has to be the same function object and there are countless leaks today of people not realizing that their removeEventListener, when they thought of doing it actually doesn't do anything.

Using objects instead of functions is a pattern inherited from Java, which is unnatural and unneeded in JavaScript.  Callbacks should simply be functions.

As I mentioned earlier, it *seems* like it would be straightforward to add handler objects to addEventListener, eg:

this.click_handler = elem.addEventListener("click", function(e) { });
this.click_handler.stop();

which would be equivalent to the corresponding removeEventListener call.  (I don't feel that strongly about this, either, since the patterns I'm using for removing listeners work quite well already, but maybe it'd be helpful for others, and we don't seem to need a new "on()" API to accomplish it.)

- Perfornance. Event Delegation as a concept has been pushed because it's slow to install all these listeners upfront. Is that being worked on?

It'll always be slower to install a thousand separate listeners than to install a delegate listener once.

Delegation also makes installing dynamic content using innerHTML much simpler, since you don't need to dig into the result to find the DOM nodes to attach listeners to.

--
Glenn Maynard

Reply | Threaded
Open this post in threaded view
|

Re: Better event listeners

Anne van Kesteren-4
In reply to this post by Jake Verbaten
On Mon, Jan 7, 2013 at 6:18 PM, Jake Verbaten <[hidden email]> wrote:
> Emphasis being on that `.on(...)` returns some kind of token that can be
> used to remove the listener so we don't have to keep a reference to the
> listener ourself somewhere.

I think that makes a lot of sense. I like the idea of just returning a
function reference.


> The other emphasis is that delegation filtering supports arbitrary functions
> which allows people to delegate  whatever they want instead of a limited
> string based DSL

What are the use cases beyond selector-based filtering?


--
http://annevankesteren.nl/

Reply | Threaded
Open this post in threaded view
|

Re: Better event listeners

Olli Pettay
In reply to this post by Glenn Maynard
On 01/08/2013 03:02 AM, Glenn Maynard wrote:
> On Sun, Jan 6, 2013 at 7:01 AM, Olli Pettay <[hidden email] <mailto:[hidden email]>> wrote:
>
>     Really? How would it work in a sane way with mouseenter/leave which behavior
>     is largely about non-bubbling.
>
>
> I'm not sure I understand the question.  For event delegation, you want mouseenter to bubble, just like any other event.


If you handle mouseenter/leave in bubble phase, you're pretty much forced to check the target.
But sure, based on your comment below these new kinds of listeners which get all the events, target checking
would need to happen most cases anyway


>
> Note that event not bubbling is only a convenience.  It's trivial to get the same effect in an event listener without it: if(e.eventPhase !=
> Event.AT_TARGET) return;
>
>     Or perhaps you don't meant normal bubbling but you mean that if there is
>     this kind of special event listener in an ancestor, such listener should be
>     called even is the event doesn't bubble.
>
>
> Firing event listeners on ancestors, after calling it on the target, is exactly what bubbling is.
>
> All this would mean (regardless of whether via an addEventListener flag, as I suggested, or implicitly with another method, as Anne suggested), is
> that all events would bubble, but event listeners would only be fired during the bubble phase if either 1: the event's bubbles flag is set, or 2: the
> event listener's always-bubbles flag is set.  The result would be identical for existing code (as it needs to be, of course), since listeners with the
> current APIs would always have their always-bubbles flags unset.
>
>
> On Sun, Jan 6, 2013 at 7:02 AM, Olli Pettay <[hidden email] <mailto:[hidden email]>> wrote:
>
>     Or just use capturing event listeners.
>
>
> I think this can only be recommended as a solution if it's correct to *always* use capture for the delegation pattern.  If you need to carefully use
> capture for some cases and not for others, for the same basic pattern, it'd be better to solve the bubbles flag problem itself.
>
> (I don't feel too strongly about this, but if we're going to do it at all, I think extending addEventListener to take a dictionary and make this an
> option is better than adding a new entry point with subtly-different behavior.  It's too subtle a difference, so it'll probably confuse and bite people.)
>
>
> On Mon, Jan 7, 2013 at 11:12 AM, Benoit Marchant <[hidden email] <mailto:[hidden email]>> wrote:
>
>     - How about a better way when your handlers are objects and not functions? The big problem with functions is removing the listeners, it has to be
>     the same function object and there are countless leaks today of people not realizing that their removeEventListener, when they thought of doing it
>     actually doesn't do anything.
>
>
> Using objects instead of functions is a pattern inherited from Java, which is unnatural and unneeded in JavaScript.  Callbacks should simply be functions.
>
> As I mentioned earlier, it *seems* like it would be straightforward to add handler objects to addEventListener, eg:
>
> this.click_handler = elem.addEventListener("click", function(e) { });
> this.click_handler.stop();
>
> which would be equivalent to the corresponding removeEventListener call.  (I don't feel that strongly about this, either, since the patterns I'm using
> for removing listeners work quite well already, but maybe it'd be helpful for others, and we don't seem to need a new "on()" API to accomplish it.)
>
>     - Perfornance. Event Delegation as a concept has been pushed because it's slow to install all these listeners upfront. Is that being worked on?
>
>
> It'll always be slower to install a thousand separate listeners than to install a delegate listener once.
>
> Delegation also makes installing dynamic content using innerHTML much simpler, since you don't need to dig into the result to find the DOM nodes to
> attach listeners to.
>
> --
> Glenn Maynard
>


Reply | Threaded
Open this post in threaded view
|

Re: Better event listeners

Glenn Maynard
In reply to this post by Anne van Kesteren-4
On Tue, Jan 8, 2013 at 5:23 AM, Anne van Kesteren <[hidden email]> wrote:
On Mon, Jan 7, 2013 at 6:18 PM, Jake Verbaten <[hidden email]> wrote:
> Emphasis being on that `.on(...)` returns some kind of token that can be
> used to remove the listener so we don't have to keep a reference to the
> listener ourself somewhere.

I think that makes a lot of sense. I like the idea of just returning a
function reference.

I don't think there's much benefit to doing something unusual here.  Returning an object with a method is the common pattern on the platform, and doesn't assume we'll never want to add other related features in the future (eg. Prototype's also has start(), to re-add the listener).

--
Glenn Maynard

Reply | Threaded
Open this post in threaded view
|

Re: Better event listeners

Glenn Maynard
In reply to this post by Anne van Kesteren-4
On Tue, Jan 8, 2013 at 5:23 AM, Anne van Kesteren <[hidden email]> wrote:
On Mon, Jan 7, 2013 at 6:18 PM, Jake Verbaten <[hidden email]> wrote:
> Emphasis being on that `.on(...)` returns some kind of token that can be
> used to remove the listener so we don't have to keep a reference to the
> listener ourself somewhere.

I think that makes a lot of sense. I like the idea of just returning a
function reference.

Returning an object with a method is a more common pattern on the platform.  It also allows adding related features in the future without contortions (eg. Prototype's also has start(), to re-add the listener).

--
Glenn Maynard

Reply | Threaded
Open this post in threaded view
|

Re: Better event listeners

Jake Verbaten
using an object also allows for future scope creep. Returning a function makes the API simple and reduces complexity.

However almost none of the existing host APIs are higher order functions (I can't think of a single API that returns a function) so it probably doesn't fit with the style.

However returning an object with a method stop is ambigious, returning an object with a method removeEventListener is verbose. I guess an object with a cancel method would make sense.


On Tue, Jan 8, 2013 at 3:32 PM, Glenn Maynard <[hidden email]> wrote:
On Tue, Jan 8, 2013 at 5:23 AM, Anne van Kesteren <[hidden email]> wrote:
On Mon, Jan 7, 2013 at 6:18 PM, Jake Verbaten <[hidden email]> wrote:
> Emphasis being on that `.on(...)` returns some kind of token that can be
> used to remove the listener so we don't have to keep a reference to the
> listener ourself somewhere.

I think that makes a lot of sense. I like the idea of just returning a
function reference.

Returning an object with a method is a more common pattern on the platform.  It also allows adding related features in the future without contortions (eg. Prototype's also has start(), to re-add the listener).

--
Glenn Maynard


Reply | Threaded
Open this post in threaded view
|

Re: Better event listeners

Glenn Maynard
On Tue, Jan 8, 2013 at 5:38 PM, Jake Verbaten <[hidden email]> wrote:
using an object also allows for future scope creep. Returning a function makes the API simple and reduces complexity.

What you're suggesting is intentionally using a limited mechanism to prevent the addition of future features, under the assumption that anything we might want to add in the future is bad.  I hope you'll reconsider that argument.  :)  Reject features after they've been proposed and considered--not before they've even been thought of.

It's not complex to return an object.  It's a universal concept on the platform.

--
Glenn Maynard

Reply | Threaded
Open this post in threaded view
|

Re: Better event listeners

Brandon Wallace
In reply to this post by Jake Verbaten
An object with a "dispose" is a common choice.  knockoutjs & Reactive Extensions both use it to unsubscribe.  The name is not terribly verbose and has a general enough meaning that it can be applied to many different concepts, including stop listening to an event listener.

If you choose a method name that is "common" with other patterns in use, then it allows for easier interoperability.  For example, if you indeed chose to return a "Disposable", then developers can leverage existing libraries to help manage their event listeners.  Here's a simplistic example that makes use of Rx.CompositeDisposable* to manage subscriptions to some knockout observables, rx observables, and event handlers:

var allMySubscriptions = new Rx.CompositeDisposable();
allMySubscriptions.add(someKnockoutObservable.subscribe(function (value) { ... }));
allMySubscriptions.add(someRxObservable.subscribe(function (value) { ... }, function (error) { ... }, function () { ... }));
allMySubscriptions.add(addEventListener("click", function (ev) { ... }));

...elsewhere...
allMySubscriptions.dispose(); // code making this call doesn't even need to know what is being disposed.

Works because all involved APIs return an object that has a "dispose" method to end the subscription.

https://github.com/Reactive-Extensions/RxJS/blob/master/src/core/disposables/compositedisposable.js

Brandon



________________________________
From: Jake Verbaten <[hidden email]>
To: Glenn Maynard <[hidden email]>
Cc: Anne van Kesteren <[hidden email]>; "[hidden email]" <[hidden email]>
Sent: Tuesday, January 8, 2013 5:38 PM
Subject: Re: Better event listeners


using an object also allows for future scope creep. Returning a function makes the API simple and reduces complexity.


However almost none of the existing host APIs are higher order functions (I can't think of a single API that returns a function) so it probably doesn't fit with the style.

However returning an object with a method stop is ambigious, returning an object with a method removeEventListener is verbose. I guess an object with a cancel method would make sense.



On Tue, Jan 8, 2013 at 3:32 PM, Glenn Maynard <[hidden email]> wrote:

On Tue, Jan 8, 2013 at 5:23 AM, Anne van Kesteren <[hidden email]> wrote:

>
>On Mon, Jan 7, 2013 at 6:18 PM, Jake Verbaten <[hidden email]> wrote:
>>> Emphasis being on that `.on(...)` returns some kind of token that can be
>>> used to remove the listener so we don't have to keep a reference to the
>>> listener ourself somewhere.
>>
>>I think that makes a lot of sense. I like the idea of just returning a
>>function reference.
>>
>
>Returning an object with a method is a more common pattern on the platform.  It also allows adding related features in the future without contortions (eg. Prototype's also has start(), to re-add the listener).
>--
>Glenn Maynard
>
>

Reply | Threaded
Open this post in threaded view
|

RE: Better event listeners

Domenic Denicola
What about just... "off"?

> -----Original Message-----
> From: Brandon Wallace [mailto:[hidden email]]
> Sent: Tuesday, January 8, 2013 21:20
> To: Jake Verbaten; Glenn Maynard
> Cc: Anne van Kesteren; [hidden email]
> Subject: Re: Better event listeners
>
> An object with a "dispose" is a common choice.  knockoutjs & Reactive
> Extensions both use it to unsubscribe.  The name is not terribly verbose and
> has a general enough meaning that it can be applied to many different
> concepts, including stop listening to an event listener.
>
> If you choose a method name that is "common" with other patterns in use,
> then it allows for easier interoperability.  For example, if you indeed chose to
> return a "Disposable", then developers can leverage existing libraries to help
> manage their event listeners.  Here's a simplistic example that makes use of
> Rx.CompositeDisposable* to manage subscriptions to some knockout
> observables, rx observables, and event handlers:
>
> var allMySubscriptions = new Rx.CompositeDisposable();
> allMySubscriptions.add(someKnockoutObservable.subscribe(function (value)
> { ... })); allMySubscriptions.add(someRxObservable.subscribe(function (value)
> { ... }, function (error) { ... }, function () { ... }));
> allMySubscriptions.add(addEventListener("click", function (ev) { ... }));
>
> ...elsewhere...
> allMySubscriptions.dispose(); // code making this call doesn't even need to
> know what is being disposed.
>
> Works because all involved APIs return an object that has a "dispose" method
> to end the subscription.
>
> * https://github.com/Reactive-
> Extensions/RxJS/blob/master/src/core/disposables/compositedisposable.js
>
> Brandon
>
>
>
> ________________________________
> From: Jake Verbaten <[hidden email]>
> To: Glenn Maynard <[hidden email]>
> Cc: Anne van Kesteren <[hidden email]>; "[hidden email]" <www-
> [hidden email]>
> Sent: Tuesday, January 8, 2013 5:38 PM
> Subject: Re: Better event listeners
>
>
> using an object also allows for future scope creep. Returning a function makes
> the API simple and reduces complexity.
>
>
> However almost none of the existing host APIs are higher order functions (I
> can't think of a single API that returns a function) so it probably doesn't fit
> with the style.
>
> However returning an object with a method stop is ambigious, returning an
> object with a method removeEventListener is verbose. I guess an object with a
> cancel method would make sense.
>
>
>
> On Tue, Jan 8, 2013 at 3:32 PM, Glenn Maynard <[hidden email]> wrote:
>
> On Tue, Jan 8, 2013 at 5:23 AM, Anne van Kesteren <[hidden email]>
> wrote:
> >
> >On Mon, Jan 7, 2013 at 6:18 PM, Jake Verbaten <[hidden email]>
> wrote:
> >>> Emphasis being on that `.on(...)` returns some kind of token that
> >>> can be used to remove the listener so we don't have to keep a
> >>> reference to the listener ourself somewhere.
> >>
> >>I think that makes a lot of sense. I like the idea of just returning a
> >>function reference.
> >>
> >
> >Returning an object with a method is a more common pattern on the
> platform.  It also allows adding related features in the future without
> contortions (eg. Prototype's also has start(), to re-add the listener).
> >--
> >Glenn Maynard
> >
> >
>

Reply | Threaded
Open this post in threaded view
|

Re: Better event listeners

Jonas Sicking-2
In reply to this post by Anne van Kesteren-4
On Jan 5, 2013 4:28 AM, "Anne van Kesteren" <[hidden email]> wrote:
>
> We discussed this a long time ago. Lets try again. I think ideally we
> introduce something like http://api.jquery.com/on/ Lets not focus on
> the API for now, but on what we want to accomplish.
>
> Type and callback are the basics. Selector-based filtering of
> event.currentTarget should be there, but optional. I'm not sure about
> the data feature, Yehuda?

What I understand many people to do is to attach an event listener at
the root of a subtree (often the document) and then filter based on if
the clicked (or whatever the event represents) matches a given
selector. The effect of that is similar to if you had attached an
event listener to all elements matching a given selector, and keep
registering and unregistering as elements are added and removed.

In order for that to work you need to do selector-based filtering of
event.target, not event.currentTarget.

> Should we make all events bubble for the purposes of this new
> registration mechanism? I actually thought Jonas said jQuery did that
> at some point, but the jQuery documentation does not suggest it does.

The pattern described above only works on events that bubble. I think
we've received pressure every now and then from authors to make
certain events bubble just so that they can use that pattern. Note
that this is also useful if you aren't doing selector-based matching,
but rather want to be notified any time some particular event happens.

In general I think the original DOM specs got it right when they
created the capture, target and bubble phases. And when they realized
that for some events you only want to listen to the target, and for
some you want to listen both on target and on ancestors.

However I think they used the wrong solution by making the distinction
solely based on what the type of the event is. While the type of the
event certainly is a good indicator for if an event handler is most
likely going to want to listen only to events fired at a particular
node, or on all events fired on a subtree, there are exceptions.

Attaching event handlers in a sub tree and then doing filtering based
on selectors or node names to only catch events fired at certain
targets, seems like a good example of such an exception.

It has been suggested (both here and elsewhere, including by me) that
you can use capturing event listeners to implement a
catch-all-in-subtree listener. However I've been convinced that this
isn't a good solution.

Capturing event handlers were created to permit a generic top-level
handlers which override event handlers on descendants, typically on
the target itself. By using .preventDefault() and .defaultPrevented
the capturing event handler can signal to event handlers on the target
that it already has handled the event and that no further action
should be taken.

Bubbling event handlers allow the opposite. I.e. a generic top-level
handler which only take action if event handlers on descendants hasn't
already handled the event.

By telling people that they have to use capturing handlers we make
this impossible. Generic handlers on ancestors would always execute
before more specific handlers on the target.

So I don't think that we want to force people to use capturing
listeners any time they want to catch all events targeted at a
particular subtree.

Instead we should make it possible to at the time of registration,
select whether the listener wants to listen to during the bubbling,
target or capture phase. And maybe allow multiple phases. We could
certainly have defaults based on the event type, but I think it should
be possible to override that default.


On the subject of things that we want to accomplish with this new API:

One thing not mentioned in your original email which I *think* might
be nice to accomplish would be to allow more OOP-style use of events,
but with JS flavor. In particular, what a lot of people do right now
is to write code like:

foo.addEventListener("click", myHandlerFunction);
function myHandlerFunction(event) { ... };

Or

foo.addEventListener("click", function(event) { ... });

In other words, they pass a function as the event handler.

Unfortunately javascript doesn't let you do

foo.addEventListener("click", object.clickhandler);

If you do that, the "this" object when clickhandler is called won't be
|object| but rather |foo|. Instead people end up doing

foo.addEventListener("click", object.clickhandler.bind(object));

which is pretty verbose.

It would be nice if it was possible to automatically register a set of
functions on an object to as listener for a set of events. And that
the functions were dispatched such that the 'this' object was the
"correct" object. In other words, that you could actually use OOP
style for your code. Especially since a lot of large JS codebases use
OOP style to a large extent. Which shouldn't be surprising given that
JS is, you know, a OOP based language :)

There are many ways to accomplish this, and I don't want to make a
specific proposal here since this thread is about gathering
requirements not proposals. But I also don't want people to dismiss
this idea just because they don't like specific ways to accomplish
this.

So here are a couple of potential solutions:

function MyClass(state) {
  this._state = state;
}
MyClass.prototype = {
  onclick: function(event) { ... },
  ondblclick: function(event) { ... },
  somehelperfunction: function(x) { ... }
}
element.on(["click", "dblclick"], new MyClass(1234));


or


function MyClass(state) {
  this._state = state;
}
MyClass.prototype = {
  onclick: function(event) { ... },
  ondblclick: function(event) { ... },
  somehelperfunction: function(x) { ... }
}
element.on(new MyClass(1234));
// we enumerate all properties of the object and add event listener
based on property names starting with "on".


or


function MyClass(state) {
  this._state = state;
}
MyClass.prototype = {
  onclick: function(event) { ... },
  myDoubleClickHandler: function(event) { ... },
  somehelperfunction: function(x) { ... }
}
element.on({ click: "onclick", dblclick: "myDoubleClickHandler" }, new
MyClass(1234));


/ Jonas

Reply | Threaded
Open this post in threaded view
|

Re: Better event listeners

Glenn Maynard
On Wed, Jan 9, 2013 at 8:35 PM, Jonas Sicking <[hidden email]> wrote: 
If you do that, the "this" object when clickhandler is called won't be
|object| but rather |foo|. Instead people end up doing

foo.addEventListener("click", object.clickhandler.bind(object));

which is pretty verbose.

I don't think this is harmfully verbose.  It's not nearly as nice as Python's mechanism (which automatically does the bind for you), but it's not a big deal--not enough to justify a whole new distinct API, at least.

It would be nice if it was possible to automatically register a set of
functions on an object to as listener for a set of events. And that
the functions were dispatched such that the 'this' object was the
"correct" object. In other words, that you could actually use OOP
style for your code. Especially since a lot of large JS codebases use
OOP style to a large extent. Which shouldn't be surprising given that
JS is, you know, a OOP based language :)

I don't think this is a problem that should be solved at this level, since it applies to every callback-based API.  It's the same as having to say "setInterval(this.update.bind(this), 1000)".  If there's to be any solution for this (and I'm not sure there needs to be), it should be at the language level, not whack-a-moled at the API level.

But I've really never found saying ".bind(this)" to be a burden.  It's a minor annoyance at worst.

(I also don't think I agree that passing in an object implementing a bunch of event listeners as methods is any more "OOP style" than passing in a function, FWIW.)

MyClass.prototype = {
  onclick: function(event) { ... },
  ondblclick: function(event) { ... },
  somehelperfunction: function(x) { ... }
}

In general I don't like these, partly due to the above, and partly because I don't see any real benefit to justify adding such another distinct way of doing the same task, which is a big cost.  It also seems fairly complex and unlike anything else on the platform that I know of, and makes everyone learn another API (a greater cost than all the times I've typed ".bind(this)" in my career combined).

--
Glenn Maynard

123