Source: util/events.js

/**
 * Provides methods for handling events fired by Bitbucket Server, and for triggering your own events.
 *
 * **This module is available synchronously.**
 *
 * **Web Resource:** com.atlassian.bitbucket.server.bitbucket-web-api:events
 *
 * @module bitbucket/util/events
 * @namespace bitbucket/util/events
 */
import eve from 'eve';

/**
 * A utility object allowing you to attach handlers to multiple events and detach them all at once.
 *
 * NOTE: The constructor is not exposed. Use {@linkcode bitbucket/util/events.chain} or {@linkcode bitbucket/util/events.chainWith}
 * to obtain an instance.
 *
 * @class EventChain
 * @memberof bitbucket/util/events
 */

/**
 * An interface for objects that accept event handlers. Used with {@linkcode bitbucket/util/events.chainWith}.
 *
 * @typedef {Object} EventProducer
 * @memberof bitbucket/util/events
 * @property {bitbucket/util/events.EventProducerOn} on - Accept an event listener who will be called when an event occurs.
 * @property {bitbucket/util/events.EventProducerOff} off - Detach an event listener who will no longer be called when an event occurs.
 */

/**
 * @callback EventProducerOn
 * @memberof bitbucket/util/events
 * @param {string} eventName - The type of event to attach the listener to.
 * @param {function} eventListener - The listener to attach.
 */

/**
 * @callback EventProducerOff
 * @memberof bitbucket/util/events
 * @param {string} eventName - The type of event to detach the listener from.
 * @param {function} eventListener - The listener to detach.
 */

const events = {
    /**
     * Trigger a global event.
     *
     * @memberOf bitbucket/util/events
     *
     * @param {string} eventName - The name of the event to fire. This should be a '.'-delimited namespace.
     * @param {Object} context - The context to fire the event with. Handlers will be called with the context as `this`.
     * Any further params will be used as arguments to the handlers.
     */
    trigger: function (eventName, context /*, ...args*/) {
        return eve.apply(this, arguments);
    },
    /**
     * Call a function every time an event is fired.
     *
     * @memberOf bitbucket/util/events
     *
     * @param {string} eventName - The name of the event to handle. This should be a '.'-delimited namespace.
     *                           You can replace any component of the namespace with a '*' for wildcard matching.
     * @param {function} fn - The handler function to call when the event is fired.
     */
    on: function (eventName, fn) {
        return eve.on(eventName, fn);
    },
    /**
     * Stop calling a function when an event is fired. The function is assumed to have previously been passed to
     * `.on` or `.once`
     *
     * @memberOf bitbucket/util/events
     *
     * @param {string} eventName - The name of the event to stop handling. This should be a '.'-delimited namespace.
     *                           You can replace any component of the namespace with a '*' for wildcard matching.
     * @param {function} fn - The handler function to stop calling when the event is fired.
     */
    off: function (eventName, fn) {
        return eve.off(eventName, fn);
    },
    /**
     * Call a function the first time an event is fired.
     *
     * @memberOf bitbucket/util/events
     *
     * @param {string} eventName - The name of the event to handle once. This should be a '.'-delimited namespace.
     *                           You can replace any component of the namespace with a '*' for wildcard matching.
     * @param {function} fn - The handler function to call the first time the event is fired.
     */
    once: function (eventName, fn) {
        return eve.once(eventName, fn);
    },
    /**
     * Return all handlers that would be triggered when an event is fired.
     *
     * @memberOf bitbucket/util/events
     *
     * @param {string} eventName - The name of the event to return handlers for.
     * @return {Array.<function>} - An array of handler functions.
     */
    listeners: function (eventName) {
        return eve.listeners(eventName);
    },
    /**
     * Determine the current event name or whether the current event name includes a specific sub-name.
     *
     * @memberOf bitbucket/util/events
     *
     * @param {string=} subname - The sub-name to search for in the current event name (optional).
     * @return {string|boolean} Either returns the name of the currently firing event, or if a sub-name is passed in
     *                          it instead returns whether this event includes that sub-name.
     */
    name: function (subname) {
        return eve.nt(subname);
    },
    /**
     * Creates an event object that tracks all listeners and provides a convenience function {@linkcode bitbucket/util/events.EventChain#destroy}
     * that will stop listening to all events in the chain, rather than manually having to call {@linkcode bitbucket/util/events.off}
     * again.
     *
     * @memberOf bitbucket/util/events
     *
     * @example
     *     var chain = events.chain().on('a', callback).on('b', callback);
     *     ...
     *     chain.destroy();
     *
     * @returns {bitbucket/util/events.EventChain}
     */
    chain: function () {
        return this.chainWith(this);
    },
    /**
     * Works exactly like {@linkcode bitbucket/util/events.chain}, but allows you to specify your own
     * event producer to attach and detach listeners on. The event producer must conform to the
     * {@linkcode bitbucket/util/events.EventProducer} interface.
     *
     * @memberOf bitbucket/util/events
     *
     * @example
     *     var chain = events.chainWith($('.something')).on('a', callback).on('b', callback);
     *     ...
     *     chain.destroy();
     *
     * @param {bitbucket/util/events.EventProducer} that - An object on which to attach/detach event listeners.
     * @returns {bitbucket/util/events.EventChain}
     */
    chainWith: function (that) {
        var listeners = [];
        var eventChain =
            /**
             * @lends bitbucket/util/events.EventChain.prototype
             */
            {
                /**
                 * @param {string} eventName - The type of event to attach the listener to.
                 * @param {function} eventListener - The listener to attach.
                 * @returns {bitbucket/util/events.EventChain}
                 */
                on: function (eventName, eventListener) {
                    var args = arguments;
                    that.on.apply(that, args);
                    listeners.push(function () {
                        that.off.apply(that, args);
                    });

                    return this;
                },
                /**
                 * Handle the first time an event is fired.
                 *
                 * @param {string} eventName - The type of event to attach the listener to.
                 * @param {function} eventListener - The listener to attach.
                 * @returns {bitbucket/util/events.EventChain}
                 */
                once: function (eventName, eventListener) {
                    var listener = function () {
                        that.off(eventName, listener);

                        return eventListener.apply(this, arguments);
                    };
                    that.on(eventName, listener);
                    listeners.push(function () {
                        that.off(eventName, listener);
                    });

                    return this;
                },
                /**
                 * Destroy the event chain and detach all listeners. The chain should not be used after calling this method.
                 */
                destroy: function () {
                    // I would use map except that I want to avoid dependencies
                    for (var i = 0; i < listeners.length; i++) {
                        listeners[i]();
                    }
                    // Just in case someone wants to keep using it
                    listeners = [];
                },
            };

        return eventChain;
    },
};

export default events;