Home Reference Source

osjs-event-emitter/index.js

/*
 * OS.js - JavaScript Cloud/Web Desktop Platform
 *
 * Copyright (c) Anders Evenrud <andersevenrud@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author  Anders Evenrud <andersevenrud@gmail.com>
 * @licence Simplified BSD License
 */

/*
 * Gets an array of event names based in input,
 * be it array or a comma separated string.
 */
const getEventNames = name => (name instanceof Array)
  ? name
  : String(name).replace(/\s+/g, '').split(',');

/**
 * Event Emitter
 *
 * @desc A standards compatible event handler (observer) with some sugar.
 */
export class EventEmitter {

  /**
   * Create Event Handler
   * @param {string} [name] A name for logging
   */
  constructor(name = 'undefined') {
    /**
     * The name of the handler
     * @type {string}
     */
    this.name = name;

    /**
     * Registered events
     * @type {object}
     */
    this.events = {};
  }

  /**
   * Destroys all events
   */
  destroy() {
    this.events = {};
  }

  /**
   * Add an event handler
   *
   * You can supply an array of event names or a comma separated list with a string
   *
   * @param {string|string[]} name Event name
   * @param {Function} callback Callback function
   * @param {object} [options] Options
   * @param {boolean} [options.persist] This even handler cannot be removed unless forced
   * @param {boolean} [options.once] Fire only once
   * @return {EventEmitter} Returns current instance
   */
  on(name, callback, options = {}) {
    options = options || {};

    if (typeof callback !== 'function') {
      throw new TypeError('Invalid callback');
    }

    getEventNames(name).forEach(n => {
      if (!this.events[n]) {
        this.events[n] = [];
      }

      this.events[n].push({callback, options});
    });

    return this;
  }

  /**
   * Adds an event handler that only fires once
   * @return {EventEmitter} Returns current instance
   */
  once(name, callback) {
    return this.on(name, callback, {once: true});
  }

  /**
   * Removes an event handler
   *
   * If no callback is provided, all events bound to given name will be removed.
   *
   * You can supply an array of event names or a comma separated list with a string
   *
   * @param {string|string[]} name Event name
   * @param {Function} [callback] Callback function
   * @param {boolean} [force=false] Forces removal even if set to persis
   * @return {EventEmitter} Returns current instance
   */
  off(name, callback = null, force = false) {
    getEventNames(name)
      .filter(n => !!this.events[n])
      .forEach(n => {
        if (callback) {
          let i = this.events[n].length;
          while (i--) {
            const ev = this.events[n][i];
            const removable = !ev.options.persist || force;
            if (removable && ev.callback === callback) {
              this.events[n].splice(i, 1);
            }
          }
        } else {
          this.events[n] = force
            ? []
            : this.events[n].filter(({options}) => options.persist === true);
        }
      });

    return this;
  }

  /**
   * Emits an event
   *
   * You can supply an array of event names or a comma separated list with a string
   *
   * @param {string|string[]} name Event name
   * @param {*} [args] Arguments
   * @return {EventEmitter} Returns current instance
   */
  emit(name, ...args) {
    getEventNames(name).forEach(n => {
      if (this.events[n]) {
        let i = this.events[n].length;
        while (i--) {
          const {options, callback} = this.events[n][i];

          try {
            callback(...args);
          } catch (e) {
            console.warn(e);
          }

          if (options && options.once) {
            this.events[n].splice(i, 1);
          }
        }
      }
    });

    return this;
  }

}