/**
* @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 */
};