Source: Tracker/triggers.js

/**
 * @class
 * @name TriggersManager
 * @public
 * @description
 * <h2>Triggers manager</h2>
 * <br />At any moment of the javascript tag, library triggers can occur. It's possible to subscribe to it with the
 * methods 'on' (see below) by giving them the trigger's name in question and the function to be called. The
 * trigger name got a simple syntax : strings separated by a colon (':'). This sequence of trigger parts can be considered
 * as a chaptering from the broader to the most detailed.
 * <br />It allows a granular handling of the triggers: 'hit:send:ok', 'hit:send:error' and 'hit:send:warning' are all
 * contained in the main event 'hit:*'. It's then possible to subscribe to a specific trigger or to a family of trigger.
 * <br /><br /><b>Listeners structure</b>
 * <br />To structure the listeners we use a tree
 * <br />If we take as an example the addition of these triggers:
 * <br />- 'a:a:a'
 * <br />- 'b:a'
 * <br />- 'c:a'
 * <br />- 'c:a:a'
 * <br />- 'c:a:b'
 * <br />- 'c:a:c'
 * <br />- 'c:b'
 * <br /><br />The following tree is obtained:
 * <pre>
 *         a -- a -- a
 *       /
 *      /
 * root -- b -- a
 *      \           a
 *       \        /
 *         c -- a - b
 *           \    \
 *            \     c
 *             \
 *              b
 * </pre>
 * <br />Each node (even root) corresponds to a listener which therefore contains an array of functions to execute when passing through this node.
 * <br /><br /><b>IMPORTANT REMINDER</b>: If trigger 'c:a:b' is raised then all functions which are part of root, root:c, root:c:a et root:c:a:b are executed.
 * <br /><br />Finally, here is the object corresponding to the tree represented previously (the property '*' corresponds to the value of the node):
 * <pre>
 * {
 *     "*": [],
 *     "a": {
 *         "*": [],
 *         "a": {
 *             "*": [],
 *             "a": {"*": []}
 *         }
 *     },
 *     "b": {
 *         "*": [],
 *         "a": {"*": []}
 *     },
 *     "c": {
 *         "*": [],
 *         "a": {
 *             "*": [],
 *             "a": {"*": []},
 *             "b": {"*": []},
 *             "c": {"*": []}
 *         },
 *         "b": {"*": []}
 *     }
 * }
 * </pre>
 */
var TriggersManager = function () {
    'use strict';
    var self = this;

    // explication sur la structuration de cet objet à la fin du code

    /**
     * This variable contains all triggers with their listeners
     * @memberof TriggersManager#
     * @type {object}
     * @private
     */
    var listeners = {};

    /**
     * Execute a list of functions with the parameters trigger and val.
     * @memberof TriggersManager#
     * @function
     * @param funcs {Array} List of functions to call
     * @param trigger {string} Trigger parameter to add
     * @param val {*} Data parameter to add
     * @returns {Array} List of functions after execution
     * @private
     */
    function map(funcs, trigger, val) {
        var newFuncArray = [];
        for (var i = 0; i < funcs.length; i++) {
            funcs[i].callback(trigger, val);
            if (!funcs[i].singleUse) newFuncArray.push(funcs[i]);
        }
        return newFuncArray;
    }

    /**
     * Add listener on trigger parts.
     * @memberof TriggersManager#
     * @function
     * @param triggerParts {Array}
     * @param remainingTree {Array}
     * @param callback {function}
     * @param singleUse {boolean} If the callback method must be called once (the first time)
     * @returns {number} Callback ID
     * @private
     */
    function addListener(triggerParts, remainingTree, callback, singleUse) {
        var triggerPart = triggerParts.shift();
        if (triggerPart === '*') {
            //on ne va pas plus loin
            //on colle val dans le tableau de *
            remainingTree['*'] = remainingTree['*'] || [];
            remainingTree['*'].push({'callback': callback, 'singleUse': singleUse});
            return (remainingTree['*'].length - 1);
        } else if (triggerParts.length === 0) {
            //on ajoute une étoile à la fin pour faire cohérent et on rappelle la fonction
            return addListener([triggerPart, '*'], remainingTree, callback, singleUse);
        } else {
            remainingTree['*'] = remainingTree['*'] || [];
            remainingTree[triggerPart] = remainingTree[triggerPart] || {};
            return addListener(triggerParts, remainingTree[triggerPart], callback, singleUse);
        }
    }

    /**
     * Process list of listeners.
     * @memberof TriggersManager#
     * @function
     * @param trigger {string} Trigger parameter to add
     * @param data {object} Object parameter to add
     * @private
     */
    function execListeners(trigger, data) {
        if (listeners['*']) {
            listeners['*'] = map(listeners['*'], trigger, data);
        }
        execListeners2(trigger, trigger.split(":"), listeners, data);
    }

    /**
     * Execute list of listeners.
     * @memberof TriggersManager#
     * @function
     * @param trigger {string} Trigger parameter to add
     * @param triggerParts {Array}
     * @param remainingTree {Array}
     * @param data {object} Object parameter to add
     * @private
     */
    function execListeners2(trigger, triggerParts, remainingTree, data) {
        var triggerPart = triggerParts.shift();
        if (triggerPart === '*') {
            //rien, on a fini!
        } else if (triggerParts.length === 0) {
            execListeners2(trigger, [triggerPart, '*'], remainingTree, data);
        } else {
            if (remainingTree[triggerPart]) {
                remainingTree[triggerPart]['*'] = map(remainingTree[triggerPart]['*'], trigger, data);
                execListeners2(trigger, triggerParts, remainingTree[triggerPart], data);
            }
        }
    }

    /**
     * Subscribe to a trigger.
     * @name on
     * @memberof TriggersManager#
     * @function
     * @param trigger {string} Trigger you want to subscribe
     * @param callback {function} Method that you want to be triggered
     * @param singleUse {boolean} if the callback method must be called once (the first time)
     * @public
     */
    self['on'] = function (trigger, callback, singleUse) {
        singleUse = singleUse || false;
        return addListener(trigger.split(":"), listeners, callback, singleUse);
    };

    /**
     * Throw a trigger.
     * @name emit
     * @memberof TriggersManager#
     * @function
     * @param trigger {string} Trigger that you want to emit
     * @param data {object} Data you want to transmit to listeners
     * @public
     */
    self['emit'] = function (trigger, data) {
        execListeners(trigger, data);
    };

    /* @if test */
    self['listeners'] = function(){return listeners};
    /* @endif */
};