Skip to content
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

Allow objects as event handlers #1735

Open
mgol opened this issue Oct 20, 2014 · 16 comments · May be fixed by #5024
Open

Allow objects as event handlers #1735

mgol opened this issue Oct 20, 2014 · 16 comments · May be fixed by #5024
Assignees
Labels
Milestone

Comments

@mgol
Copy link
Member

mgol commented Oct 20, 2014

Originally reported by petka_antonov@… at: http://bugs.jquery.com/ticket/12031

Doing OOP with jQuery events requires a lot of $.proxy boilerplate. the .addEventListener interface allows passing an object as a handler like so:

function SomeWidget( elem ) {
    this.elem = elem;
}

SomeWidget.prototype = {
    constructor: SomeWidget,

```
renderTo: function( target ) {
    $(target).append(this.elem);
    this.elem.addEventListener( "click", this );
    this.elem.addEventListener( "mousemove", this );
},

mousemove: function( e ) {

},

click: function( e ) {

},

handleEvent: function( e ) {
    return this[e.type].apply( this, arguments );
}
```

};

With jQuery you have to do this:

function SomeWidget( elem ) {
    this.elem = elem;
    //Each method needs to be bound to the instance. This also has overhead of creating many functions.
    this.mousemove = $.proxy( this.mousemove, this );
    this.click = $.proxy( this.click, this );
}

SomeWidget.prototype = {
constructor: SomeWidget,

renderTo: function( target ) {
    $(target).append(this.elem);
    $(this.elem).on( {
        click: this.click,
        mousemove: this.mousemove
    });
},

mousemove: function( e ) {

},

click: function( e ) {

}

};


With jQuery supporting objects as event handlers, it would work like:

function SomeWidget( elem ) {
    this.elem = elem;
}

SomeWidget.prototype = {
constructor: SomeWidget,

renderTo: function( target ) {
    $(target).append(this.elem);
    $(this.elem).on( "click mousemove", this );
},

mousemove: function( e ) {

},

click: function( e ) {

},

handleEvent: function( e ) {
    return this[e.type].apply( this, arguments );
}

};


And that's it. No $.proxy hacks, no redundant creation of functions just to retain binding.

Issue reported for jQuery 1.7.2

@mgol mgol added this to the 3.0.0 milestone Oct 20, 2014
@mgol
Copy link
Member Author

mgol commented Oct 20, 2014

Comment author: dmethvin

Worth thinking about so I'll leave it open.

@mgol
Copy link
Member Author

mgol commented Oct 20, 2014

Comment author: rwaldron

I'm into this

 http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventListener

@dmethvin - if you don't mind, I'd like to prototype this one :)

@mgol
Copy link
Member Author

mgol commented Oct 20, 2014

Comment author: rwaldron

note to self...

 .on({ click: fn }) 

Has to support handling:

// delegated events
.on({ click: fn }, "div");
// data object events
.on({ click: fn }, "div", dataobject);

@mgol
Copy link
Member Author

mgol commented Oct 20, 2014

Comment author: dmethvin

Bulk change from enhancement to feature.

@mgol
Copy link
Member Author

mgol commented Oct 20, 2014

Comment author: dmethvin

gnarf and I talked about this in Austin. It would be a very low-level public interface, ideally the current event features would be built on top of it. Essentially replace  this function so we don't go to the current jQuery dispatcher and instead use a caller-supplied dispatching object. That would provide users with low-overhead ways to handle high-frequency events like scroll or mousemove (or the upcoming pointermove) by avoiding jQuery.event.fix() for example.

@mgol
Copy link
Member Author

mgol commented Oct 20, 2014

Comment author: gibson042

A good implementation would make something like  https://github.com/jquery/jquery/pull/1367/files significantly more lightweight.

@mgol
Copy link
Member Author

mgol commented Oct 20, 2014

Comment author: dmethvin

Additional note re #14953, can we have a way to do capturing here? As I understand the .addEventListener interface it seems like all the events in the attached object would be capturing.

@mgol
Copy link
Member Author

mgol commented Oct 20, 2014

Comment author: petka_antonov@…

In addEventListener the optional useCapture parameter defines whether capturing is in use regardless if you use a function or an object as a handler. The default is false so in the example all events are bubbling.

@mgol
Copy link
Member Author

mgol commented Oct 20, 2014

Comment author: dmethvin

Re rwaldron's comment 5 above, I am thinking this interface will be low level enough that we will not support any type of enhancement. That includes special events, delegation, fixing, or additional data. To get those things you'll either need to do them yourself or use the standard interfaces.

For the additional data issue, it's probably not an issue since the this points to an object you get to populate. The other features cost execution time and one of the reasons for this interface is to provide a clean way to get performance when you don't need things like event normalization across browsers.

@dmethvin
Copy link
Member

Some notes on what I've realized/discovered as I've been thinking on this ticket for implementation in 3.0, to write it down somewhere.

  • Although the object can easily be set up as @petkaantonov describes above, each individual event must be attached via an elem.addEventListener( eventType, handlerFunction, useCapture ) call.
  • This method is low level but we want it to work properly with methods like .clone( true ) (cloning events to the new element) and .remove() (removing all listeners). The caller cannot set these up through the native addEventListener because jQuery needs to maintain bookkeeping information inside the object.
  • It's actually possible to attach two handlers per event type, one for each possible useCapture value, so that complicates the bookkeeping.
  • I'd like to use this interface as the foundation for our existing event layer. We need to define hooks that are called in the object on methods like .clone() or .remove() that can manage the jQuery handlers.

@mgol
Copy link
Member Author

mgol commented Sep 14, 2015

@dmethvin do you plan to finish it in time for 3.0.0? Since this looks like a non-breaking change it seems we could defer it to 3.1.0 (please update the milestone if you agree).

@mgol mgol modified the milestones: 3.1.0, 3.0.0 Sep 16, 2015
@mgol
Copy link
Member Author

mgol commented Sep 16, 2015

@dmethvin Bumped to 3.1.0 but feel free to do it before if you want. ;)

@php4fan
Copy link

php4fan commented Mar 19, 2024

10 years later, is there still no support whatsoever for useCapture??

@mgol mgol modified the milestones: 4.1.0, 5.0.0 Mar 19, 2024
@mgol
Copy link
Member Author

mgol commented Mar 19, 2024

We are currently targeting an event module rewrite that may allow the capture mode among other things for 5.0.0. We originally planned this for 4.0.0 but it didn't make the cut and we didn't want to delay the release any further.

@MarkMYoung
Copy link

I don't know if this will help, but I wrote a custom EventTarget class back before custom events were allowed. It was written to follow the specification (at that time) exactly, including useCapture. https://github.com/MarkMYoung/EventTargetImpl

@dmethvin
Copy link
Member

So the main obstacle for jQuery is that, almost 20 years ago, there was a decision to have jQuery track all events on a DOM element using the same internal data structure. That made sense (at the time) for performance and cross-browser compatibility reasons. The problem is that now, a particular event handler can have options like capture, once, signal, passive, or other future options, that prevent the use of a multiplexed handler. So, jQuery needs to change its internal data structures to track each event separately with its options, rather than grouping them all together.

This change is complicated because some unknown number of other projects have come to depend on inspecting jQuery's internal data structures to figure out what jQuery handlers are attached to an element. When I looked a few years ago, those projects included the Firefox dev tools. We'd prefer not to break the universe when we add support for new features. Plus, we still have to ensure that jQuery's other event methods like .trigger() and .triggerHandler() continue to work, or document why they no longer work in particular cases.

There are ways to support these new options today in jQuery using custom events, but since the events are multiplexed it means that all the events of the same type on an element will end up having the same options. https://jsbin.com/bupesajoza/edit?html,js,output

I have never wanted capture/passive listeners in frameworks like React or Vue, but they do support them--I had to go look it up to be sure because I'd never needed them! Due to the way elements are defined in markup for those frameworks, it's much less likely that you'd run into the jQuery situation of "select elements and attach a new event handler with an unusual option when there may be handlers on that same element with other options". So if that same one-option-per-element limitation isn't a problem, jQuery's custom event handler isn't a bad solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

Successfully merging a pull request may close this issue.

5 participants