modules/PubSub.js

// @ts-check
import '../types'

export default class PubSub {
  subscribers = {}

  constructor({ DS }) {
    this.DS = DS
  }

  /**
   * Subscribe to an event
   * @memberof DragSelect#
   * @function subscribe
   * @param {DSCallbackNames} eventName
   * @param {DSCallback} callback
   * @returns {number} event id, can be used to unsubscribe more efficiently
   */
  subscribe = (eventName, callback) => {
    if (!Array.isArray(this.subscribers[eventName]))
      this.subscribers[eventName] = []
    this.subscribers[eventName].push(callback)
    return this.subscribers[eventName].length - 1
  }

  /**
   * Removes event subscription
   * @memberof DragSelect#
   * @function unsubscribe
   * @param {DSCallbackNames} eventName
   * @param {DSCallback} [callback] the callback method signature, has to be exactly the same as when subscribing. Consider using "id" instead.
   * @param {number} [id] event id returned when subscribed (more performant than callback search)
   */
  unsubscribe = (eventName, callback, id) => {
    if (id >= 0) this.subscribers[eventName].splice(id, 1)
    else if (callback)
      this.subscribers[eventName] = this.subscribers[eventName].filter(
        (cb) => cb !== callback
      )
  }

  /**
   * Publishes an event to all subscribers
   * @memberof DragSelect#
   * @function publish
   * @param {DSCallbackNames|DSCallbackNames[]} eventName
   * @param {CallbackObject} data passed to the subscription method
   */
  publish = (eventName, data) => {
    if (Array.isArray(eventName))
      eventName.forEach(name => this._publish(name, data))
    else
      this._publish(eventName, data)
  }
  _publish = (eventName, data) => {
    const subscribers = this.subscribers[eventName]
    if (!Array.isArray(subscribers)) return
    if (eventName.includes(`:pre`))
      this._handlePrePublish(subscribers, data)
    else
      this._handlePublish(subscribers, data)
  }

  // non-pre events are executed first in first out
  _handlePublish = (subscribers, data) => {
    for (let i = 0, il = subscribers.length; i < il; i++) {
      if (this.DS.stopped) return
      subscribers[i](data)
    }
  }

  // pre events are executed last in first out (so user callbacks are called before DS callbacks)
  _handlePrePublish = (subscribers, data) => {
    let i = subscribers.length
    while (i--) {
      if (this.DS.stopped) return
      subscribers[i](data)
    }
  }
}