ccampbell / gator

Event delegation in Javascript
http://craig.is/riding/gators
492 stars 46 forks source link

Sugar for one-time listeners #10

Open appsforartists opened 10 years ago

appsforartists commented 10 years ago

It would be nice for the Gator standard library to include sugar for one-time listeners, namely onFirst and onNth. Here's a sample implementation of onFirst:

Gator.prototype.onFirst = function (events, selector, callback) {
    /*  Useful abstraction for making sure something is only called
     *  once, even if the event is fired repeatedly.  For instance,
     *  onFirst('transitionend', ...) can be used to clean up a host of
     *  parallel transitions that each fire their own 'transitionend'.
     */
    var self = this;

    var listener = function (event) {
        self.off(events, selector, listener);
        callback(event);
    }

    self.on(events, selector, listener);
};

If @ccampbell approves of this sugar, I'll open a pull request.

thelinuxlich commented 10 years ago

It would be better if it didn't remove the event of all elements of the selector, just from the element which triggered the event.

lazd commented 10 years ago

@appsforartists, this is more complex than your code sample lets on. Let's say you do this:

var myElement = document.createElement('div');
var handleClick = function(event) {
  console.log('Clicked!');
};

// Let's handle only the first click to myElement
Gator(myElement).onFirst('click', handleClick);

// We've changed our mind, we don't want to handle that click anymore
Gator(myElement).off('click', handleClick);

// But alas, when the user clicks, handleClick is still called!

It doesn't work, because handleClick is not the listener that was passed to on(). With the approach you've outlined, it's impossible for the user to remove that event listener because they don't have a reference to the listener function created inside of onFirst.

One solution to this problem would be to maintain a WeakMap so you could find the actual function given the user-provided function. However, I don't think this is a feature that Gator should be concerned with -- this is not event delegation, -1.

With Gator in its current form, users can implement this functionality very easily using named functions:

var myElement = document.createElement('div');

// Pass a named function to on()
Gator(myElement).on('click', function handleClick(event) {
  console.log('Clicked!');
  // handleClick will refer to this function, so we can easily remove the listener
  Gator(myElement).off('click', handleClick);
});
englishextra commented 7 years ago

@lazd That's because the handleClick handler should be defined in Gator, not outside. This is a common thing when you face problems with removing one-time listeners.

lazd commented 7 years ago

@englishextra I think we're talking about two different things. Look at the code in the original issue, it passes listener to gator.on(), not the user-provided callback. As such, you cannot pass callback to gator.off() to remove it, you would have to pass listener, which obviously you don't have access to as it was created inside of gator.onFirst() and never returned. Because of this, it is impossible to remove a listener added with gator.onFirst() if it is implemented as described in the issue.

englishextra commented 7 years ago

@lazd Oh yeah

Then I guess this has to be established as a good rule for that issue:

It doesn't work, because handleClick is not the listener that was passed to on(). With the approach you've outlined, it's impossible for the user to remove that event listener because they don't have a reference to the listener function created inside of onFirst.