Search code examples
javascriptlottiebodymovin

Self canceling events using bind


What I'm looking for here is a method to refer to a bound method from within that method for the purpose of removing an eventlistener from within the event listener.

I'd like to create a single method to handle the same action over a few different events.

I've got a function that handles rotating the elements called rotateActiveItem. It looks at a list of list items and activates one at a time.

In my constructor I'd like to set up a few, potentially many, events that trigger rotateLoadingCopy.

this.oneQuarter = genericDurationEvent.bind(this, animationDuration * .25);
this.half = genericDurationEvent.bind(this, animationDuration * .5);
this.threeQuarters = genericDurationEvent.bind(this, animationDuration * .75);

Each of these are added to the animation's events:

this.animation_.addEventListener('enterFrame', this.oneQuarter);
this.animation_.addEventListener('enterFrame', this.half);
this.animation_.addEventListener('enterFrame', this.threeQuarters);

And then the event checks for duration, executes the rotation, and then should remove itself from the eventListeners.

genericDurationEvent(duration, event) {
  if (event.currentTime >= duration) {
    // Activate the next loading text element.
    this.rotateActiveItem();

    // Stop listening for this event.
    this.animation_.removeEventListener('enterFrame', /*What goes here?*/);
  }
}

At first I thought maybe I could bind the bound method onto another method, but that's a rabbit hole of bound functions.

Then I thought arguments.callee would do this, but I'm in strict mode it its deprecated in strict mode.


Solution

  • I would recommend going for a closure pattern, and generating the handlers dynamically, so you can keep a reference to the function.

    genericDurationEvent(context, duration) {
      var handler = function (event) {
        if (event.currentTime >= duration) {
          // Activate the next loading text element.
          this.rotateActiveItem();
    
          // Stop listening for this event.
          this.animation_.removeEventListener('enterFrame', handler);
        }
      }.bind(context);
      return handler;
    }
    
    this.oneQuarter = genericDurationEvent(this, animationDuration * .25);
    this.half = genericDurationEvent(this, animationDuration * .5);
    this.threeQuarters = genericDurationEvent(this, animationDuration * .75);
    

    If you wanna go the extra mile, you could isolate the logic in a way that the generator makes any function behave like "once" ala jQuery#one():

    // definition
    function once (what, eventName, originalHandler) {
      function onceHandler (...args) {
        what.removeEventListener(eventName, onceHandler);
        return originalHandler.apply(this, args);
      };
      what.addEventListener(eventName, onceHandler);
    }
    
    // usage
    once(this.animation_, 'enterFrame', event => {
      if (event.currentTime >= animationDuration * .25) {
        // Activate the next loading text element.
        this.rotateActiveItem();
      }
    });