Source: event/EventEmitter.ts

import { TypeVoidFunction } from '../types/voidFunction';
import { noop } from '../functions';
import { IEvent } from './types';

/**
 * EventEmitter 의 기능과 동일한 맥락으로, 간단히 구성
  <iframe
    src="https://codesandbox.io/embed/nonollcode-snippet-9gko8?autoresize=1&expanddevtools=1&fontsize=14&hidenavigation=1&initialpath=%2Fevent-EventEmitter.html&module=%2Fevent-EventEmitter.html&theme=dark"
    style="width:100%; height:500px; border:1px solid black; border-radius: 4px; overflow:hidden;"
    title="@nonoll/code-snippet"
    allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media; usb"
    sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"
  ></iframe>
 * @export
 * @class EventEmitter
 * @alias event/EventEmitter
 * @memberof event
 * @see https://www.npmjs.com/package/events
 * @example
import { EventEmitter } from '@nonoll/code-snippet/event';

const createElement = ({ tag = 'div', id = '', style = '', value = '', text = '' }) => {
  const doc = window.document;
  const target = doc.createElement(tag);
  target.setAttribute('id', id);
  target.setAttribute('style', style);
  target.setAttribute('value', value);
  if (text) {
    target.textContent = text;
  }
  return target;
}

let ee;
const forExample = () => {
  const doc = window.document;

  const onButton = createElement({ tag: 'button', text: 'on button' });
  const offButton = createElement({ tag: 'button', text: 'off button' });
  const emitButton = createElement({ tag: 'button', text: 'emit button' });
  const EVENT_NAME = 'EVENT_EXAMPLE';

  doc.body.appendChild(onButton);
  doc.body.appendChild(offButton);
  doc.body.appendChild(emitButton);

  ee = new EventEmitter();
  ee.on(EVENT_NAME, res => console.log(res));

  onButton.addEventListener('click', e => {
    e.preventDefault();
    console.log('onButton clicked');
    if (!ee) {
      return;
    }
    ee.off(EVENT_NAME);
    ee.on(EVENT_NAME, res => console.log(res));
  });

  offButton.addEventListener('click', e => {
    e.preventDefault();
    console.log('offButton clicked');
    if (!ee) {
      return;
    }
    ee.off(EVENT_NAME);
  });

  emitButton.addEventListener('click', e => {
    e.preventDefault();
    console.log('emitButton clicked');
    if (!ee) {
      return;
    }
    ee.emit(EVENT_NAME, new Date());
  });
}

forExample();
 */
export class EventEmitter {
  private events: Array<IEvent> = [];

  constructor() {}

  /**
   * 이벤트 감지 등록
   * @param {string} eventName
   * @param {TypeVoidFunction} [listener={@link noop}]
   * @param {*} [context]
   * @returns {EventEmitter}
   * @memberof EventEmitter
   */
  on(eventName: string, /* istanbul ignore next: for noop */ listener: TypeVoidFunction = noop, context?: any): EventEmitter {
    this.events.push({ eventName, listener, context });
    return this;
  }

  /**
   * 이벤트 감지 해제
   * @param {string} eventName
   * @param {TypeVoidFunction} [listener]
   * @returns {EventEmitter}
   * @memberof EventEmitter
   */
  off(eventName: string, listener?: TypeVoidFunction): EventEmitter | false {
    // tslint:disable-next-line: max-line-length
    const matched = this.events.findIndex(regEvent => regEvent.eventName === eventName && (listener ? regEvent.listener === listener : true));
    // tslint:disable-next-line: no-bitwise
    if (!~matched) {
      return false;
    }
    this.events.splice(matched, 1);
    return this;
  }

  /**
   * 이벤트 1회 감지 등록
   * @param {string} eventName
   * @param {TypeVoidFunction} [listener={@link noop}]
   * @param {*} [context]
   * @returns {EventEmitter}
   * @memberof EventEmitter
   */
  once(eventName: string, /* istanbul ignore next: for noop */ listener: TypeVoidFunction = noop, context?: any): EventEmitter {
    const onceWrapper = (...values: any) => {
      this.off(eventName, onceWrapper);
      listener.apply(context || listener, values);
    };
    this.on(eventName, onceWrapper, context);
    return this;
  }

  /**
   * 이벤트 전파
   * @param {string} eventName
   * @param {...any[]} values
   * @returns {EventEmitter}
   * @memberof EventEmitter
   */
  emit(eventName: string, ...values: any[]): EventEmitter {
    this.events.forEach(regEvent => {
      if (regEvent.eventName === eventName) {
        regEvent.listener.apply(regEvent.context || regEvent.listener, values);
      }
    });
    return this;
  }

  /**
   * 이벤트 전파
   * @param {string} eventName
   * @param {any} values
   * @returns {EventEmitter}
   * @memberof EventEmitter
   */
  fire(eventName: string, values: any): EventEmitter {
    this.emit(eventName, values);
    return this;
  }
}