Source: Utils/utils.js

/**
 * @class
 * @classdesc Plugin with utils methods.
 * @name Utils
 * @memberof ATInternet.Tracker.Plugins
 * @type {function}
 * @param tag {object} Instance of the Tag used
 * @description
 * This plugin shares specific feature methods to our helper plugins.
 * For example plugins Page, Clicks and OnSiteAds have common usages.
 * @public
 */
ATInternet.Tracker.Plugins.Utils = function (tag) {

    'use strict';

    var self = this;

    // storage for the querystrings processed, key is the hash of the querystring, value is an object with all querystring parameters
    var _queryStringValues = {};

    var _debug = {
        level: 'DEBUG',
        messageEnd: 'method ended'
    };

    /**
     * [Object added by plugin {@link ATInternet.Tracker.Plugins.Utils Utils}]
     * @name utils
     * @memberof ATInternet.Tracker.Tag
     * @inner
     * @type {object}
     * @property {function} get Tag helper, see details here {@link ATInternet.Tracker.Tag#utils.getQueryStringValue}
     * @property {function} getPrivate Tag helper, see details here {@link ATInternet.Tracker.Tag#utils.getLocation}
     * @public
     */
    tag.utils = {};


    /**
     * Get the value of a queryString variable
     * @name getQueryStringValue
     * @memberof ATInternet.Tracker.Plugins.Utils#
     * @function
     * @param name {string} Variable that you want to get
     * @param str {string} String in which we will look the wanted variable (considered as queryString format)
     * @return string|null|undefined {string|null|undefined} This method returns a string if the variable exists with a value,
     * it returns null if the variable is not present (or not correct for a queryString) or
     * it returns undefined if the variable exists but doesn't have any value (ex : '&variable=')
     * @public
     */
    tag.utils.getQueryStringValue = self.getQueryStringValue = function (name, str) {
        var hash = ATInternet.Utils.hashcode(str).toString();
        if (!_queryStringValues[hash]) {
            _queryStringValues[hash] = {};
            var regex = new RegExp('[&#?]{1}([^&=#?]*)=([^&#]*)?', 'g');
            var match = regex.exec(str);
            while (match !== null) {
                _queryStringValues[hash][match[1]] = match[2];
                match = regex.exec(str);
            }
        }
        if (_queryStringValues[hash].hasOwnProperty(name)) {
            return _queryStringValues[hash][name];
        } else {
            return null;
        }
    };
    /**
     * Check an iterative property in an object and build a specific string from it
     * @name manageChapters
     * @memberof ATInternet.Tracker.Plugins.Utils#
     * @function
     * @param tagObject {object} Object including properties to be checked
     * @param key {string} Key to be checked
     * @param length {number|string} Number of occurences
     * @return {string}
     * @example
     * <pre>
     * var myObject = {
     *     chapter1:'one',
     *     chapter2:'two'
     * }
     * var chapters = manageChapters(myObject, 'chapter', 2);
     * // chapters = 'one::two::'
     * </pre>
     * @public
     */
    tag.utils.manageChapters = self.manageChapters = function (tagObject, key, length) {
        var fullChapter = '';
        if (tagObject) {
            var ignoreEmpty = tag.getConfig('ignoreEmptyChapterValue');
            var chapterLabel = '';
            for (var i = 1; i < parseInt(length, 10) + 1; i++) {
                chapterLabel = tagObject[key + i] || '';
                if (ignoreEmpty) {
                    fullChapter += chapterLabel ? chapterLabel + '::' : '';
                } else {
                    fullChapter += tagObject.hasOwnProperty(key + i) ? chapterLabel + '::' : '';
                }
            }
        }
        return fullChapter;
    };

    /**
     * Get document level to use from global configuration or the default one (document.)
     * @name getDocumentLevel
     * @memberof ATInternet.Tracker.Plugins.Utils#
     * @function
     * @return {object}
     * @public
     */
    tag.utils.getDocumentLevel = self.getDocumentLevel = function () {
        var documentLevel = tag.getConfig('documentLevel');
        if (documentLevel) {
            if (documentLevel.indexOf('.') < 0) {
                return (window[documentLevel] || document);
            } else {
                var tab = documentLevel.split('.');
                return (window[tab[0]][tab[1]] || document);
            }
        }
        return document;
    };

    /**
     * Get the current location (URL associated with the document level configured)
     * @name getLocation
     * @memberof ATInternet.Tracker.Plugins.Utils#
     * @function
     * @return {string}
     * @public
     */
    tag.utils.getLocation = self.getLocation = function () {
        var obj = self.getDocumentLevel();
        return obj.location.href;
    };

    /**
     * Get the current location (URL associated with the document level configured)
     * @name getLocation
     * @memberof ATInternet.Tracker.Plugins.Utils#
     * @function
     * @return {string}
     * @public
     */
    tag.utils.getHostName = self.getHostName = function () {
        var obj = self.getDocumentLevel();
        return obj.location.hostname;
    };

    //////////////////////////////  dispatch  ///////////////////////////////////

    tag.dispatchIndex = {};
    tag.dispatchStack = [];
    tag.dispatchEventFor = {};
    var ExecWaitingSpecificDispatchCount = 0;

    /**
     * [Function added by plugin {@link ATInternet.Tracker.Plugins.Utils Utils}] Add a helper plugin into a list telling which ones have been used before dispatching
     * @name dispatchSubscribe
     * @memberof ATInternet.Tracker.Tag
     * @inner
     * @function
     * @param helperName {string} Plugin name to add
     * @return {boolean}
     * @private
     */
    tag.dispatchSubscribe = function (helperName) {
        if (!tag.dispatchIndex[helperName]) {
            tag.dispatchStack.push(helperName);
            tag.dispatchIndex[helperName] = true;
            return true;
        }
        return false;
    };

    /**
     * [Function added by plugin {@link ATInternet.Tracker.Plugins.Utils Utils}] Return a boolean which tell if the helper plugin concerned is waiting for a dispatch
     * @name dispatchSubscribed
     * @memberof ATInternet.Tracker.Tag
     * @inner
     * @function
     * @param helperName {string} Plugin name to check
     * @return {boolean}
     * @private
     */
    tag.dispatchSubscribed = function (helperName) {
        return tag.dispatchIndex[helperName] === true;
    };

    /**
     * [Function added by plugin {@link ATInternet.Tracker.Plugins.Utils Utils}] Add a specific event for dispatching
     * @name addSpecificDispatchEventFor
     * @memberof ATInternet.Tracker.Tag
     * @inner
     * @function
     * @param helperName {string} Plugin name to check
     * @return {boolean}
     * @private
     */
    tag.addSpecificDispatchEventFor = function (helperName) {
        if (!tag.dispatchEventFor[helperName]) {
            tag.dispatchEventFor[helperName] = true;
            ExecWaitingSpecificDispatchCount++;
            return true;
        }
        return false;
    };

    /**
     * Test if specific dispatch are being processed
     * @memberof ATInternet.Tracker.Plugins.Utils#
     * @function
     * @return {boolean}
     * @private
     */
    var _IsExecWaitingSpecificDispatch = function () {
        return ExecWaitingSpecificDispatchCount !== 0;
    };

    /**
     * [Function added by plugin {@link ATInternet.Tracker.Plugins.Utils Utils}] Process a specific event for dispatching
     * @name processSpecificDispatchEventFor
     * @memberof ATInternet.Tracker.Tag
     * @inner
     * @function
     * @param helperName {string} Plugin name to check
     * @private
     */
    tag.processSpecificDispatchEventFor = function (helperName) {
        if (tag.dispatchEventFor[helperName]) {
            tag.dispatchEventFor[helperName] = false;
            ExecWaitingSpecificDispatchCount--;
            if (ExecWaitingSpecificDispatchCount === 0) {
                tag.dispatchEventFor = {};
                tag.emit('Tracker:Plugin:SpecificEvent:Exec:Complete', {lvl: 'INFO'});
            }
        }
    };

    /**
     * [Helper added by plugin {@link ATInternet.Tracker.Plugins.Utils Utils}] Dispatch all tags.
     * @name dispatch
     * @memberof ATInternet.Tracker.Tag
     * @inner
     * @function
     * @param callback {function} Callback to execute
     * @param elementType {string} Element type (mailto, form, redirection)
     * @public
     */
    tag.dispatch = function (callback, elementType) {
        var _doDispatch = function () {
            var name = '';
            var cb = null;
            while (tag.dispatchStack.length > 0) {
                name = tag.dispatchStack.pop();
                if (tag.dispatchStack.length === 0) {
                    cb = callback;
                }
                tag[name].onDispatch(cb, elementType);
            }
            tag.dispatchIndex = {};
            tag.delContext(undefined, 'customObject');
            /* @if debug */
            tag.debug('Utils:dispatch', _debug.level, _debug.messageEnd);
            /* @endif */
        };
        var _processDispatch = function () {
            if (!tag.plugins.isExecWaitingLazyloading()) {
                _doDispatch();
            } else {
                // it manages both cases, lazyload plugins can finish loading after some lazyload triggers or the contrary
                tag['onTrigger']('Tracker:Plugin:Lazyload:Exec:Complete', function () {
                    _doDispatch();
                }, true);
            }
        };
        if (!_IsExecWaitingSpecificDispatch()) {
            _processDispatch();
        } else {
            tag.onTrigger('Tracker:Plugin:SpecificEvent:Exec:Complete', function () {
                _processDispatch();
            }, true);
        }
    };

    /**
     * [Helper added by plugin {@link ATInternet.Tracker.Plugins.Utils Utils}] Dispatch all tags (with automatic redirect management).
     * @name dispatchRedirect
     * @memberof ATInternet.Tracker.Tag
     * @inner
     * @function
     * @param tagObject {object} Tag object, used here to tell how we have to do the redirection (url/target)
     * @return {boolean}
     * @public
     */
    tag.dispatchRedirect = function (tagObject) {
        var preservePropagation = true;
        var elementType = '';
        var callback = null;
        if (tagObject) {
            var eventObject = null;
            if (tagObject.hasOwnProperty('event')) {
                eventObject = tagObject.event || window.event;
            }
            if (!ATInternet.Utils.isTabOpeningAction(eventObject) && tagObject.elem) {
                tag.plugins.exec('TechClicks', 'manageClick', [tagObject.elem, eventObject], function (data) {
                    preservePropagation = data.preservePropagation;
                    elementType = data.elementType;
                });
            }
            callback = tagObject.callback;
        }
        tag.dispatch(callback, elementType);
        return preservePropagation;
    };

    //////////////////////////////  send  ///////////////////////////////////

    /**
     * [Helper added by plugin {@link ATInternet.Tracker.Plugins.Utils Utils}] Manage send depending on Safari previewing and Chrome/IE prerendering.
     * @name manageSend
     * @memberof ATInternet.Tracker.Tag
     * @inner
     * @function
     * @param callback {function} Function to execute when not in prerendering mode or on "visibilityChangeEvent"
     * @private
     */
    tag.manageSend = function (callback) {
        if (!ATInternet.Utils.isPreview() || tag.getConfig('preview')) {
            if (!ATInternet.Utils.isPrerender(function (event) {
                callback(event);
            })) {
                callback();
            }
        }
    };

    /**
     * [Helper added by plugin {@link ATInternet.Tracker.Plugins.Utils Utils}] Merge Tag object with the corresponding permanent buffered object if exists.
     * @name processTagObject
     * @memberof ATInternet.Tracker.Tag
     * @inner
     * @function
     * @param hitParam {string} Hit parameter
     * @param hitType {array} Hit types
     * @param tagObject {object} Tag object
     * @return {object}
     * @private
     */
    tag.processTagObject = function (hitParam, hitType, tagObject) {
        var bufferObject = tag.getParam(hitParam, true);
        if (bufferObject && bufferObject._options['permanent']) {
            var isValidType = false;
            var bufferObjectType = bufferObject._options['hitType'] || [];
            for (var i = 0; i < bufferObjectType.length; i++) {
                if (ATInternet.Utils.arrayIndexOf(hitType.concat('all'), bufferObjectType[i]) !== -1) {
                    isValidType = true;
                    break;
                }
            }
            if (isValidType) {
                tagObject = ATInternet.Utils.completeFstLevelObj((bufferObject._value || {}), tagObject, true);
            }
        }
        return tagObject;
    };

    /**
     * [Helper added by plugin {@link ATInternet.Tracker.Plugins.Utils Utils}] Merge object from Context with the corresponding buffered object if exists before sending hit.
     * @name processContextObjectAndSendHit
     * @memberof ATInternet.Tracker.Tag
     * @inner
     * @function
     * @param hitParam {string} Hit parameter
     * @param paramOptions {object} Parameter options
     * @param contextObject {object} Context object
     * @param callback {function} Callback to execute
     * @private
     */
    tag.processContextObjectAndSendHit = function (hitParam, paramOptions, contextObject, callback) {
        var setParamOptions = {
            hitType: paramOptions.hitType,
            encode: paramOptions.encode,
            separator: paramOptions.separator,
            truncate: paramOptions.truncate
        };
        var bufferObject = tag.getParam(hitParam, true);
        if (bufferObject) {
            var isValidType = false;
            var bufferObjectType = bufferObject._options['hitType'] || [];
            for (var i = 0; i < bufferObjectType.length; i++) {
                if (ATInternet.Utils.arrayIndexOf(paramOptions.hitType.concat('all'), bufferObjectType[i]) !== -1) {
                    isValidType = true;
                    break;
                }
            }
            if (isValidType) {
                var clonedBufferObject = ATInternet.Utils.cloneSimpleObject(bufferObject);
                clonedBufferObject._value = ATInternet.Utils.completeFstLevelObj((clonedBufferObject._value || {}), contextObject, true);
                tag.setParam(hitParam, clonedBufferObject._value, setParamOptions);
                tag.manageSend(function () {
                    tag.sendHit(null, [['hitType', paramOptions.hitType]], callback, paramOptions.requestMethod, paramOptions.elementType);
                });
                var isPermanent = bufferObject._options['permanent'];
                if (isPermanent) {
                    tag.setParam(hitParam, bufferObject._value, bufferObject._options);
                }
            } else {
                tag.setParam(hitParam, contextObject, setParamOptions);
                tag.manageSend(function () {
                    tag.sendHit(null, [['hitType', paramOptions.hitType]], callback, paramOptions.requestMethod, paramOptions.elementType);
                });
                tag.setParam(hitParam, bufferObject._value, bufferObject._options);
            }
        } else {
            tag.setParam(hitParam, contextObject, setParamOptions);
            tag.manageSend(function () {
                tag.sendHit(null, [['hitType', paramOptions.hitType]], callback, paramOptions.requestMethod, paramOptions.elementType);
            });
        }
    };

    ///////////////////////////////////////////////////////////////////////////////////////

    /* @if test */
    self.getQueryStringValuesHash = function () {
        return _queryStringValues
    };
    /* @endif */
};
ATInternet.Tracker.addPlugin('Utils');