/**
* @class
* @name BufferManager
* @public
* @param tag {object} Instance of the Tag used
* @description
* <h2>Buffer manager (data for hits)</h2>
* <br /><b>Option notion</b>
* <br />An option is the way to 'typify' a parameter. Indeed it can be very useful to specify in which hits a
* parameter must appear or how (encoding for example).
* <br /><br /><b>Example</b>
* <pre>
* Here is the list of the options :
* - encode: (boolean) if true, the parameter will be encoded
* - permanent: (boolean) if true, the parameter won't be delete after sending. So it will be sent in all other hits (if
* other options allow it).
* - separator: (char) when the parameter is an array its value in the hit is the concatenation array values separated
* by a ','. This option allow to use an other separator.
* - hitType: (Array{string}) list the hit types in which the parameter can be sent. It's possible to use a non-existent
* type of hit but here is the existing ones :
* * 'page': for impression hit
* * 'all': for all type of hit
* </pre>
* <b>Filter notion</b>
* <br />'Filters' allow to select a set of parameters by their options. One filter is formed by an option name
* and its desired value(s). The filter value MUST BE of the same type as the option value type.
* <br /><br />The 'filter' is a tuple, an Array of two elements. The first one is the option name we want to analyse, the second
* is the value(s) we want to filter.
* <br />Actually there's two cases : the option value type is a list or not. In the second case it's easy : the couple
* key/value in filter is the key/value to be retrieved. In the first case the filter will match with options which
* contain at least one of the value of the filter.
* <br />When a list of filters is used to retrieve some parameters, the result will be the intersection of the filters results.
* <br /><br /><b>Example</b>
* <pre>
* Here is a list of parameters with their options :
* param1 => {permanent: true, encode: true}
* param2 => {permanent: true, hitType: ['page', 'click']}
* param3 => {hitType: ['click']}
* param4 => {permanent: true, hitType: ['page', 'rm']}
* param5 => {hitType: ['page', 'rm']}
*
* Here is the filters results :
* [[permanent, true]] => param1, param2, param4
* [[hitType, ['page']]] => param2, param4, param5
* [[hitType, ['page', 'click']]] => param1, param2, param4, param5 (hitType 'page' OR 'click')
* [[permanent, true], [hitType, ['page']]] => param1, param2, param4 (hitType 'page' AND permanent true)
*
* If we want parameters with a hitType 'page' AND 'click' we must make TWO filter:
* [[hitType, ['page']], [hitType, ['click']]] => param2
* </pre>
*/
var BufferManager = function (tag) {
'use strict';
var self = this;
/**
* Buffer containing all hit variables with their options.
* @memberof BufferManager#
* @type {object}
* @private
*/
var _dataStore = {};
/**
* Set value for a hit variable (overrides if present).
* @name set
* @memberof BufferManager#
* @function
* @param name {string} Name of the hit variable
* @param value {string|number|function|Array} value of the hit variable
* @param options {object} Configuration of the variable, if no hitType defined, it will be "page" by default
* @public
*/
self.set = function (name, value, options) {
var bufferParam = ATInternet.Utils.privacy.testBufferParam(name, value);
if (bufferParam.toSetInBuffer) {
options = options || {};
options.hitType = options.hitType || ['page'];
_dataStore[name] = {_value: bufferParam.value, _options: options};
}
/* @if debug */
tag.debug('Tracker:Buffer:setParam', 'DEBUG', '', {
name: name,
value: value,
options: options,
buffer: _dataStore
});
/* @endif */
};
/**
* Get parameter from buffer.
* @memberof BufferManager#
* @function
* @param buffer {object} Data store
* @param key {object} Name of the property to get
* @param withOptions {boolean} If true only returns value else returns object with value and options
* @returns {string|object}
* @private
*/
var _getParam = function (buffer, key, withOptions) {
var param = ATInternet.Utils.cloneSimpleObject(buffer[key]);
if (param && !withOptions) {
return param._value;
}
return param;
};
/**
* Recursive method, which filters dataStore keys which are wanted.
* @memberof BufferManager#
* @function
* @param filterList {Array} List of key/value(s) in an array. (ex : [[key1,value1],[key2,[value2A,value2B]]]) Filter on variable's options
* @param keys {Array} List of keys in the dataStore, then for each call (recursivity) this is the keys corresponding to the previous filter
* @returns {Array} Array containing all keys of the dataStore corresponding to all filters given
* @private
*/
var _getFilteredParamKeys = function getFilteredParamKeys(filterList, keys) {
if (!filterList || !(keys instanceof Array) || !(filterList instanceof Array)) {
return [];
} else if (filterList.length === 0) {
return keys;
}
var tuple = filterList[0],
tupleValSize,
newKeys = [],
newFilterList = ATInternet.Utils.cloneSimpleObject(filterList);
newFilterList.shift();
for (var i = 0; i < keys.length; i++) {
if (typeof tuple[1] !== 'object') {// on ne regroupe pas les conditions afin de ne passer dans le else QUE si c'est un type object
if (_dataStore[keys[i]] && _dataStore[keys[i]]['_options'][tuple[0]] === tuple[1]) {
newKeys.push(keys[i]);
}
} else {
tupleValSize = tuple[1].length;
for (var j = 0; j < tupleValSize; j++) {
if (_dataStore[keys[i]] && _dataStore[keys[i]]['_options'][tuple[0]] instanceof Array &&
ATInternet.Utils.arrayIndexOf(_dataStore[keys[i]]['_options'][tuple[0]], tuple[1][j]) >= 0) {
newKeys.push(keys[i]);
break;
}
}
}
}
return getFilteredParamKeys(newFilterList, newKeys);
};
/**
* Get variables in the buffer using the filter given, with possibility of returning options or not.
* @name get
* @memberof BufferManager#
* @function
* @param filterList {Array} List of key/value(s) in an array. (ex : [[key1,value1],[key2,[value2A,value2B]]]) Filter on variable's options
* @param withOptions {boolean} If true only returns value else returns object with value and options
* @returns {string|object}
* @public
* @example
* <pre><code class="javascript">// this filter will get variables with hitType 'page' OR 'all', AND with permanent true
* var filter = [
* ['hitType',['page','all']],
* ['permanent',true]
* ]
*
* var dataObj = buffer.get(filter, true); // true to get options
* dataObj = {
* 'variableExample' : {
* _value:'value',
* _options: {
* hitType:['page'],
* permanent:true
* }
* }
* }
*
* var dataObj = buffer.get(filter); // no option in results
* dataObj = {
* 'variableExample':'value'
* }
* </code></pre>
*/
self.get = function (filterList, withOptions) {
var result = {};
if (typeof filterList === 'string') {
result = _getParam(_dataStore, filterList, withOptions);
} else {
// on filtre les clés qui respectent ce qu'on souhaite
var keys = _getFilteredParamKeys(filterList, ATInternet.Utils.getObjectKeys(_dataStore));
// on va chercher les clés filtrées dans le buffer avec ou sans leurs options
for (var index = 0; index < keys.length; index++) {
result[keys[index]] = _getParam(_dataStore, keys[index], withOptions);
}
}
return result;
};
/**
* Check if an option is handled by at least one the filters.
* @name presentInFilters
* @memberof BufferManager#
* @function
* @param filters {Array} List of buffer filters
* @param name {string} Option name
* @returns {boolean}
* @public
*/
self.presentInFilters = function (filters, name) {
if (!filters || filters.length === 0) {
return false;
}
if (filters[0][0] === name) {
return true;
} else {
return self.presentInFilters(filters.slice(1), name);
}
};
/**
* @description
* Complete the filters. In all cases, if the given option is not already handled by the filters,
* a new filter will be added.
* <br />Otherwise the behavior is different according to the type of the given value :
* <ul>
* <li>if it's an Array, all the filter which handle the option will be completed with the value. It is assumed that these filters already contains an Array value,</li>
* <li>in other cases nothing happens.</li>
* </ul>
* @name addInFilters
* @memberof BufferManager#
* @function
* @param filters {Array} List of buffer filters
* @param name {string} Option name
* @param val {Array|*}
* @param found {boolean} DO NOT SPECIFY IT
* @public
* @example
* <pre><code class="javascript">addInFilters(myfilters, 'permanent', true);
* //[] => [["permanent", true]]
* //[["hitType", ["click"]]] => [["hitType", ["click"]], ["permanent", true]]
* //[["hitType", ["page"]], ["permanent", true]] => [["hitType", ["page"]], ["permanent", true]]]
*
* addInFilters(myfilters, 'hitType', ['all']);
* //[["hitType", ["page"]] => [["hitType", ["page", "all"]]]
* //[["permanent", true]] => [["permanent", true], ["hitType", "all"]]
* //[["hitType", ["click"]], ["permanent", true]] => [["hitType", ["click", "all"]], ["permanent", true]]
* //[["hitType", ["page"]], ["permanent", true]] => [["hitType", ["page", "all"]], ["permanent", true]]
* //[["hitType", ["page"]], ["hitType", ["rm"]]] => [["hitType", ["page", "all"]], ["hitType", ["rm", "all"]]]
* //[["hitType", ["page"]], ["hitType", ["rm", "all"]]] => [["hitType", ["page", "all"]], ["hitType", ["rm", "all"]]]
* //[["hitType", ["page", "all"]], ["hitType", ["rm", "all"]]] => [["hitType", ["page", "all"]], ["hitType", ["rm", "all"]]]
* </code></pre>
*/
self.addInFilters = function (filters, name, val, found) {
if (!filters || filters.length === 0) {
if (found) {
return [];
} else {
return [[name, val]];
}
}
var fname = filters[0][0], fval = filters[0][1];
if (fname === name) {
if (fval instanceof Array && (ATInternet.Utils.arrayIndexOf(fval, val[0]) === -1)) {
fval.push(val[0]);
}
found = true;
}
return [[fname, fval]].concat(self.addInFilters(filters.slice(1), name, val, found));
};
/**
* Delete a hit variable in the buffer.
* @name del
* @memberof BufferManager#
* @function
* @param name {string} Name of the hit variable
* @public
*/
self.del = function (name) {
_dataStore[name] = undefined;
/* @if debug */
tag.debug('Tracker:Buffer:delParam', 'DEBUG', '', {name: name, buffer: _dataStore});
/* @endif */
};
/**
* Empty the buffer.
* @name clear
* @memberof BufferManager#
* @function
* @public
*/
self.clear = function () {
_dataStore = {};
};
// For unit tests on private elements !!!
/* @if test */
self._getFilteredParamKeys = _getFilteredParamKeys;
self._dataStore = _dataStore;
/* @endif */
};