Home Manual Reference Source Test Repository

src/EventProxy.js

import TyphonEvents from './TyphonEvents.js';

/**
 * EventProxy provides a protected proxy of another TyphonEvents / eventbus instance.
 *
 * The main use case of EventProxy is to allow indirect access to an eventbus. This is handy when it comes to managing
 * the event lifecycle for a plugin system. When a plugin is added it could receive a callback, perhaps named
 * `onPluginLoaded`, which contains an EventProxy instance rather than the direct eventbus. This EventProxy instance is
 * associated in the management system controlling plugin lifecycle. When a plugin is removed / unloaded the management
 * system can automatically unregister all events for the plugin without requiring the plugin author doing it correctly
 * if they had full control. IE This allows to plugin system to guarantee no dangling listeners.
 *
 * EventProxy provides the on / off, once, and trigger methods with the same signatures as found in
 * TyphonEvents / Events. However, the proxy has an internal Events instance which is used to proxy between the target
 * eventbus which is passed in from the constructor. All registration methods (on / off / once) proxy through the
 * internal Events instance using 'listenTo', 'listenToOnce', and 'stopListening'. In addition there is a `destroy`
 * method which will unregister all of proxies events and remove references to the managed eventbus. Any further usage
 * of a destroyed EventProxy instance results in a ReferenceError thrown.
 */
export default class EventProxy
{
   /**
    * Creates the event proxy with an existing instance of TyphonEvents.
    *
    * @param {TyphonEvents}   eventbus - The target eventbus instance.
    */
   constructor(eventbus)
   {
      if (!(eventbus instanceof TyphonEvents))
      {
         throw new TypeError(`'eventbus' is not an instance of TyphonEvents.`);
      }

      /**
       * Stores the target eventbus.
       * @type {TyphonEvents}
       * @private
       */
      this._eventbus = eventbus;

      /**
       * Stores the proxy instance which manages all event binding with the target eventbus.
       * @type {TyphonEvents}
       * @private
       */
      this._proxy = new TyphonEvents();
   }

   /**
    * Unregisters all proxied events from the target eventbus and removes any local references. All subsequent calls
    * after `destroy` has been called result in a ReferenceError thrown.
    */
   destroy()
   {
      if (this._proxy === null || this._eventbus === null)
      {
         throw new ReferenceError('This EventProxy instance has been destroyed.');
      }

      this._proxy.stopListening(this._eventbus);
      this._eventbus = null;
      this._proxy = null;
   }

   /**
    * Returns the target eventbus name.
    *
    * @returns {string|*}
    */
   getEventbusName()
   {
      if (this._eventbus === null) { throw new ReferenceError('This EventProxy instance has been destroyed.'); }

      return this._eventbus.getEventbusName();
   }

   /**
    * Remove a previously-bound callback function from an object. This is proxied through `stopListening` of an
    * internal Events instance instead of directly modifying the target eventbus.
    *
    * Please see {@link Events#off}.
    *
    * @param {string}   name     - Event name(s)
    * @param {function} callback - Event callback function
    * @param {object}   context  - Event context
    * @returns {EventProxy}
    */
   off(name, callback = void 0, context = void 0)
   {
      if (this._proxy === null || this._eventbus === null)
      {
         throw new ReferenceError('This EventProxy instance has been destroyed.');
      }

      this._proxy.stopListening(this._eventbus, name, callback, context);

      return this;
   }

   /**
    * Bind a callback function to an object. The callback will be invoked whenever the event is fired. If you have a
    * large number of different events on a page, the convention is to use colons to namespace them: "poll:start", or
    * "change:selection".
    *
    * This is proxied through `listenTo` of an internal Events instance instead of directly modifying the target
    * eventbus.
    *
    * Please see {@link Events#on}.
    *
    * @param {string}   name     - Event name(s)
    * @param {function} callback - Event callback function
    * @param {object}   context  - Event context
    * @returns {EventProxy}
    */
   on(name, callback, context = void 0)
   {
      if (this._proxy === null || this._eventbus === null)
      {
         throw new ReferenceError('This EventProxy instance has been destroyed.');
      }

      this._proxy.listenTo(this._eventbus, name, callback, context);

      return this;
   }

   /**
    * Just like `on`, but causes the bound callback to fire only once before being removed. Handy for saying "the next
    * time that X happens, do this". When multiple events are passed in using the space separated syntax, the event
    * will fire once for every event you passed in, not once for a combination of all events
    *
    * This is proxied through `listenToOnce` of an internal Events instance instead of directly modifying the target
    * eventbus.
    *
    * Please see {@link Events#once}.
    *
    * @param {string}   name     - Event name(s)
    * @param {function} callback - Event callback function
    * @param {object}   context  - Event context
    *
    * @returns {EventProxy}
    */
   once(name, callback, context = void 0)
   {
      if (this._proxy === null || this._eventbus === null)
      {
         throw new ReferenceError('This EventProxy instance has been destroyed.');
      }

      this._proxy.listenToOnce(this._eventbus, name, callback, context);

      return this;
   }

   /**
    * Trigger callbacks for the given event, or space-delimited list of events. Subsequent arguments to trigger will be
    * passed along to the event callbacks.
    *
    * Please see {@link Events#trigger}.
    *
    * @returns {EventProxy}
    */
   trigger()
   {
      if (this._eventbus === null) { throw new ReferenceError('This EventProxy instance has been destroyed.'); }

      this._eventbus.trigger(...arguments);

      return this;
   }

   /**
    * Provides `trigger` functionality, but collects any returned Promises from invoked targets and returns a
    * single Promise generated by `Promise.resolve` for a single value or `Promise.all` for multiple results. This is
    * a very useful mechanism to invoke asynchronous operations over an eventbus.
    *
    * Please see {@link TyphonEvents#triggerAsync}.
    *
    * @returns {Promise}
    */
   triggerAsync()
   {
      if (this._eventbus === null) { throw new ReferenceError('This EventProxy instance has been destroyed.'); }

      return this._eventbus.triggerAsync(...arguments);
   }

   /**
    * Defers invoking `trigger`. This is useful for triggering events in the next clock tick.
    *
    * Please see {@link TyphonEvents#triggerDefer}.
    *
    * @returns {EventProxy}
    */
   triggerDefer()
   {
      if (this._eventbus === null) { throw new ReferenceError('This EventProxy instance has been destroyed.'); }

      this._eventbus.triggerDefer(...arguments);

      return this;
   }

   /**
    * Provides `trigger` functionality, but collects any returned result or results from invoked targets as a single
    * value or in an array and passes it back to the callee in a synchronous manner.
    *
    * Please see {@link TyphonEvents#triggerSync}.
    *
    * @returns {*|Array.<*>}
    */
   triggerSync()
   {
      if (this._eventbus === null) { throw new ReferenceError('This EventProxy instance has been destroyed.'); }

      return this._eventbus.triggerSync(...arguments);
   }
}