Re: Better event listeners

On Jan 5, 2013 4:28 AM, "Anne van Kesteren" <annevk@annevk.nl> 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

Received on Thursday, 10 January 2013 02:36:23 UTC