/**
* @class
* @classdesc Plugins Management.
* @name PluginsManager
* @param tag {object} Instance of the Tag used
* @public
*/
var PluginsManager = function (tag) {
'use strict';
var self = this;
var pluginsLoaded = {};
var _emit = 'Tracker:Plugin:Load:';
/**
* Dictionary indexed by plugin names indicating those currently being lazyloaded (true) and
* those that were in this case (false).
* example:
* {
* 'Page': true,
* 'Click': false,
* 'SalesTracker': true
* }
* Here Page, Click and SalesTracker are "Lazyloadable" and all were called. Page and SalesTracker are still loading
* @private
*/
var pluginsInLazyloading = {};
/**
* Gives the number of plugins loading
* @private
*/
var pluginsInLazyloadingCount = 0;
/**
* Dictionary indexed by plugin names indicating those that have one or more methods
* which have been called and are waiting for execution (awaiting the end of plugin loading)
* example:
* {
* 'Page': 3,
* 'SalesTracker': 1
* }
* Page and SalesTracker have not yet been loaded but some of their methods have been called.
* Three times for the page (this may be the same method) once for SalesTracker
* @private
*/
var ExecWaitingLazyloading = {};
/**
* Gives the number of methods waiting to be executed
* @private
*/
var ExecWaitingLazyloadingCount = 0;
/**
* Check if a plugin is loaded.
* @memberof PluginsManager#
* @param name {string} Plugin's name
* @returns boolean {boolean} True if the plugin is loaded
* @private
*/
var _isLoaded = function (name) {
var isLoaded = false;
if (pluginsLoaded[name]) {
isLoaded = true;
}
return isLoaded;
};
/**
* Unload a plugin.
* @name unload
* @memberof PluginsManager#
* @function
* @param name {string} Plugin's name
* @returns parent {object} Instance of the Tag used
* @public
*/
var _unload = self['unload'] = function (name) {
if (_isLoaded(name)) {
pluginsLoaded[name] = undefined;
tag.emit('Tracker:Plugin:Unload:' + name + ':Ok', {lvl: 'INFO'});
} else {
tag.emit('Tracker:Plugin:Unload:' + name + ':Error', {lvl: 'ERROR', msg: 'not a known plugin'});
}
return tag;
};
/**
* Mark a plugin loading as finished (if exists).
* @memberof PluginsManager#
* @param name {string} Plugin's name
* @private
*/
var _lazyloadingFinished = function (name) {
if (pluginsInLazyloading[name] && _isLoaded(name)) {
pluginsInLazyloading[name] = false;
pluginsInLazyloadingCount--;
if (_isLoaded(name + '_ll')) {
_unload(name + '_ll');
}
if (pluginsInLazyloadingCount === 0) {
tag.emit('Tracker:Plugin:Lazyload:File:Complete', {
lvl: 'INFO',
msg: 'LazyLoading triggers are finished'
});
}
}
};
/**
* Load a plugin.
* @name load
* @memberof PluginsManager#
* @function
* @param name {string} Plugin's name
* @param Plugin {function} Plugin's code
* @returns parent {object} Instance of the Tag used
* @public
*/
var _load = self['load'] = function (name, Plugin) {
if (typeof Plugin === 'function') {
var allowed = (typeof tag.getConfig.plgAllowed === 'undefined') ||
(tag.getConfig.plgAllowed.length === 0) ||
(tag.getConfig.plgAllowed.indexOf(name) > -1);
if (allowed) {
pluginsLoaded[name] = new Plugin(tag);
_lazyloadingFinished(name);
tag.emit(_emit + name + ':Ok', {lvl: 'INFO'});
} else {
tag.emit(_emit + name + ':Error', {
lvl: 'ERROR',
msg: 'Plugin not allowed',
details: {}
});
}
} else {
tag.emit(_emit + name + ':Error', {
lvl: 'ERROR',
msg: 'not a function',
details: {obj: Plugin}
});
}
return tag;
};
/**
* Check if some plugin is loading.
* @name isLazyloading
* @memberof PluginsManager#
* @function
* @param name {string} Specific plugin's name
* @returns true|false {boolean} True if one or the specific plugin is loading
* @public
*/
var _isLazyloading = self['isLazyloading'] = function (name) {
if (name) {
return pluginsInLazyloading[name] === true;
} else {
return pluginsInLazyloadingCount !== 0;
}
};
/**
* Indicate if the plugin is lazyloadable and not not loaded yet.
* @memberof PluginsManager#
* @param name {string} Plugin's name
* @returns {boolean}
* @private
*/
var _isLazyLoadable = function (name) {
return !_isLoaded(name) && !_isLazyloading(name) && _isLoaded(name + '_ll');
};
/**
* Ask the lazyloading of a plugin.
* @memberof PluginsManager#
* @param name {string} Plugin name
* @private
*/
var _lazyLoad = function (name) {
pluginsInLazyloading[name] = true;
pluginsInLazyloadingCount++;
ATInternet.Utils.loadScript({url: tag.getConfig('lazyLoadingPath') + name + '.js'});
};
/**
* Lazyload a plugin if possible.
* @memberof PluginsManager#
* @param name {string} Plugin's name
* @returns true|false {boolean} False if the plugin can't be loaded (not planned for, already loaded or already in progress).
* @private
*/
var _lazyLoadIfPossible = function (name) {
if (_isLazyLoadable(name)) {
_lazyLoad(name);
return true;
} else {
return false;
}
};
/**
* Add a marker showing the method's call of a plugin which is lazyloading.
* @memberof PluginsManager#
* @param plg {string} Plugin's name
* @private
*/
var _addExecWaitingLazyloading = function (plg) {
if (!ExecWaitingLazyloading[plg]) {
ExecWaitingLazyloading[plg] = 1;
} else {
ExecWaitingLazyloading[plg]++;
}
ExecWaitingLazyloadingCount++;
};
/**
* Delete a marker showing the method's call of a plugin which is lazyloading.
* @memberof PluginsManager#
* @param plg {string} Plugin name
* @private
*/
var _delExecWaitingLazyloading = function (plg) {
ExecWaitingLazyloading[plg]--;
ExecWaitingLazyloadingCount--;
if (ExecWaitingLazyloadingCount === 0) {
tag.emit('Tracker:Plugin:Lazyload:Exec:Complete', {
lvl: 'INFO',
msg: 'All exec waiting for lazyloading are done'
});
}
};
/**
* Process lazyloading.
* @memberof PluginsManager#
* @param pluginName {string} Plugin (name) that you want to use
* @param method {string} Plugin's method that you want to use
* @param args {Array} Arguments to the method you want to use
* @param callback {function} Method which be triggered once the plugin's method has finished, it will take one argument which is what the plugin's method returned
* @private
*/
var _process = function (pluginName, method, args, callback) {
var result = null;
var tb = method.split('.');
if (_isLoaded(pluginName) && pluginsLoaded[pluginName][tb[0]]) {
if ((tb.length > 1) && pluginsLoaded[pluginName][tb[0]][tb[1]]) {
result = pluginsLoaded[pluginName][tb[0]][tb[1]].apply(pluginsLoaded[pluginName], args);
} else {
result = pluginsLoaded[pluginName][tb[0]].apply(pluginsLoaded[pluginName], args);
}
}
callback && callback(result);
};
/**
* Process lazyloading.
* @memberof PluginsManager#
* @param pluginName {string} Plugin (name) that you want to use
* @param method {string} Plugin's method that you want to use
* @param args {Array} Arguments to the method you want to use
* @param callback {function} Method which be triggered once the plugin's method has finished, it will take one argument which is what the plugin's method returned
* @private
*/
var _waitingLazyloadingProcess = function (pluginName, method, args, callback) {
_addExecWaitingLazyloading(pluginName);
tag.onTrigger(_emit + pluginName + ':Ok', function () {
_process(pluginName, method, args, function (result) {
_delExecWaitingLazyloading(pluginName);
callback && callback(result);
});
}, true);
};
/**
* Get missing dependencies.
* @memberof PluginsManager#
* @param listDep {array} Dependency name
* @private
*/
var _missingDependencies = function (listDep) {
var missing = {'mcount': 0, 'plugins': {}};
for (var i = 0; i < listDep.length; i++) {
if (!pluginsLoaded.hasOwnProperty(listDep[i])) {
missing.mcount++;
missing.plugins[listDep[i]] = true;
}
}
return missing;
};
/**
* Indicate that at least one method is waiting the end of its plugin's lazyloading.
* @name isExecWaitingLazyloading
* @memberof PluginsManager#
* @function
* @returns {boolean}
* @public
*/
self.isExecWaitingLazyloading = function () {
return ExecWaitingLazyloadingCount !== 0;
};
/**
* Execute a plugin method.
* @name exec
* @memberof PluginsManager#
* @function
* @param pluginName {string} Plugin (name) that you want to use
* @param method {string} Plugin's method that you want to use
* @param args {Array} Arguments to the method you want to use
* @param callback {function} Method which be triggered once the plugin's method has finished, it will take one argument which is what the plugin's method returned
* @public
*/
tag.exec = self.exec = function (pluginName, method, args, callback) {
if (_isLazyLoadable(pluginName)) {
_waitingLazyloadingProcess(pluginName, method, args, callback);
_lazyLoad(pluginName);
} else if (_isLazyloading(pluginName)) {
_waitingLazyloadingProcess(pluginName, method, args, callback);
} else {
_process(pluginName, method, args, callback);
}
};
/**
* Execute a callback only when all required plugins are loaded.
* @name waitForDependencies
* @memberof PluginsManager#
* @function
* @param dependencies {Array} Names of the required plugins
* @param callback {function} Method which be triggered once the dependencies will be loaded
* @public
*/
self.waitForDependencies = function (dependencies, callback) {
var missing = _missingDependencies(dependencies);
if (missing.mcount === 0) {
tag.emit('Tracker:Plugin:Dependencies:Loaded', {
lvl: 'INFO',
details: {dependencies: dependencies}
});
callback();
} else {
for (var name in missing.plugins) {
if (missing.plugins.hasOwnProperty(name)) {
tag.emit('Tracker:Plugin:Dependencies:Error', {
lvl: 'WARNING',
msg: 'Missing plugin ' + name
});
// On s'inscrit sur l'évènement de chargement du plugin manquant
tag.onTrigger(_emit + name, function (trig, data) {
var trigArr = trig.split(":");
var plugName = trigArr[3];
var state = trigArr[4];
if (state === 'Ok') {
missing.plugins[plugName] = false;
missing.mcount--;
// Si tous les plugins attendus ont été chargés, on exécute le callback
if (missing.mcount === 0) {
callback();
}
}
}, true);
// Si le plugins est lazyloadable (et qu'il n'est pas en cours de lazyloading) on demande son chargement
_lazyLoadIfPossible(name);
}
}
}
};
/**
* Create an instance of each plugin already in the prototype.
* Essential for the inclusion of the plugins already loaded.
* @name init
* @memberof PluginsManager#
* @function
* @public
*/
self.init = function () {
for (var key in ATInternet.Tracker.pluginProtos) {
if (ATInternet.Tracker.pluginProtos.hasOwnProperty(key)) {
_load(key, ATInternet.Tracker.pluginProtos[key]);
}
}
};
/* @if test */
self._isLoaded = _isLoaded;
self._unload = _unload;
self._lazyloadingFinished = _lazyloadingFinished;
self._load = _load;
self._isLazyloading = _isLazyloading;
self._isLazyLoadable = _isLazyLoadable;
self._lazyLoad = _lazyLoad;
self._lazyLoadIfPossible = _lazyLoadIfPossible;
self._addExecWaitingLazyloading = _addExecWaitingLazyloading;
self._delExecWaitingLazyloading = _delExecWaitingLazyloading;
self._process = _process;
self._waitingLazyloadingProcess = _waitingLazyloadingProcess;
self._missingDependencies = _missingDependencies;
self._pluginsLoaded = function () {
return pluginsLoaded;
};
/* @endif */
};