Source: Tracker/utils.js

/**
 * @class
 * @classdesc Utility methods.
 * @name Utils
 * @public
 */
var Utils = function () {
    var self = this;

    /**
     * Serialize any element.
     * @memberof Utils#
     * @param obj {object} Object to serialize
     * @returns {string}
     * @private
     */
    function serialJSON(obj) {
        var t = typeof (obj);
        if (t !== 'object' || obj === null) {
            if (t === 'string') {
                obj = '"' + obj + '"';
            }
            return String(obj);
        } else {
            var n, v, json = [],
                arr = (obj && obj.constructor === Array);
            for (n in obj) {
                if (obj.hasOwnProperty(n)) {
                    v = obj[n];
                    t = typeof (v);
                    if (t !== "function" && t !== "undefined") {
                        if (t === 'string') {
                            v = '"' + v.replace(/[^\\]"/g, '\\"') + '"';
                        } else if (t === 'object' && v !== null) {
                            v = serialJSON(v);
                        }
                        json.push((arr ? '' : '"' + n + '":') + String(v));
                    }
                }
            }
            return (arr ? '[' : '{') + String(json) + (arr ? ']' : '}');
        }
    }

    /**
     * Parse any string.
     * @memberof Utils#
     * @param data {string} Object to serialize
     * @returns {*|Boolean}
     * @private
     */
    function parseJSON(data) {
        if (data === null) {
            return data;
        }
        if (typeof data === "string") {
            return ( new Function("return " + data) )();
        }

        return false;
    }

    //On ignore volontairement le deuxième paramètre qui nous est réservé :)
    /**
     * Copy an object which doesnt' contain functions/objects.
     * @name cloneSimpleObject
     * @memberof Utils#
     * @inner
     * @function
     * @param inst {object} Item that you want to clone
     * @param delUndefined {boolean} If true, undefined properties are not cloned
     * @returns newInst {object} clone of the instance
     * @public
     */
    self.cloneSimpleObject = function clone(inst, delUndefined) {
        /*Si l'instance source n'est pas un objet ou qu'elle ne vaut rien c'est une feuille donc on la retourne*/
        if (typeof (inst) !== 'object' || inst === null || inst instanceof Date) {
            return inst;
        }
        /*On appel le constructeur de l'instance source pour crée une nouvelle instance de la même classe*/
        var newInst = new inst.constructor || inst.constructor();
        /*On parcourt les propriétés de l'objet et on les recopies dans la nouvelle instance*/
        for (var i in inst) {
            if (inst.hasOwnProperty(i)) {
                //On gère le cas où il a été demandé (delUndefined) de ne pas copier les propriétés undefined
                if (i !== undefined && (!delUndefined || inst[i] !== undefined)) {
                    newInst[i] = clone(inst[i]);
                }
            }
        }
        /*On retourne la nouvelle instance*/
        return newInst;
    };

    /**
     * Serialize any element. Use JSON.stringify if possible.
     * @name jsonSerialize
     * @memberof Utils#
     * @function
     * @param obj {object} Javascript object that you want to serialize to JSON
     * @returns string {string} Serialized JSON
     * @public
     */
    self.jsonSerialize = function (obj) {
        if (typeof JSON !== 'undefined' && JSON.stringify) {
            return JSON.stringify(obj);
        } else {
            return serialJSON(obj);
        }
    };

    /**
     * Parse a string. Use JSON.parse if possible.
     * @name jsonParse
     * @memberof Utils#
     * @function
     * @param str {string} JSON string that you want to parse to Javascript object
     * @returns {object}
     * @public
     */
    self.jsonParse = function (str) {
        try {
            if (typeof JSON !== 'undefined' && JSON.parse) {
                return JSON.parse(str);
            } else {
                return parseJSON(str);
            }
        }
        catch (e) {
            return null;
        }
    };

    /**
     * Search an element in an array. Use Array.indexOf if possible.
     * @name arrayIndexOf
     * @memberof Utils#
     * @function
     * @param arr {Array}
     * @param elemToSearch {*}
     * @returns {number}
     * @public
     */
    self.arrayIndexOf = function (arr, elemToSearch) {
        if (Array.indexOf) {
            return arr.indexOf(elemToSearch);
        } else {
            return (function (searchElement) {
                "use strict";
                if (this == null) {
                    throw new TypeError();
                }
                var t = Object(this);
                var len = t.length >>> 0;
                if (len === 0) {
                    return -1;
                }
                var n = 0;
                if (arguments.length > 1) {
                    n = Number(arguments[1]);
                    if (n != n) { // shortcut for verifying if it's NaN
                        n = 0;
                    } else if (n != 0 && n != Infinity && n != -Infinity) {
                        n = (n > 0 || -1) * Math.floor(Math.abs(n));
                    }
                }
                if (n >= len) {
                    return -1;
                }
                var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
                for (; k < len; k++) {
                    if (k in t && t[k] === searchElement) {
                        return k;
                    }
                }
                return -1;
            }).apply(arr, [elemToSearch]);
        }
    };

    /**
     * Generate UUID.
     * @name uuid
     * @memberof Utils#
     * @function
     * @returns {function}
     * @public
     */
    self.uuid = function () {

        /**
         * Generate GUID with alphanumeric characters (v4 format).
         * @name _v4
         * @memberof Utils#
         * @function
         * @returns {string}
         * @public
         */
        function _v4() {
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
                var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
                return v.toString(16);
            });
        }

        /**
         * Generate GUID with numeric characters.
         * @name _num
         * @memberof Utils#
         * @function
         * @param len {number} Length of the generated GUID
         * @returns {string}
         * @public
         */
        function _num(len) {
            var d = new Date();
            var format = function (a) {
                a = a - Math.floor(a / 100) * 100;
                if (a < 10) {
                    return '0' + a;
                } else {
                    return a;
                }
            };
            var rand = function (n) {
                return Math.floor((Math.random() * 9 + 1) * Math.pow(10, n - 1));
            };
            return format(d.getHours()) + '' + format(d.getMinutes()) + '' + format(d.getSeconds()) + '' + rand(len - 6);
        }

        return {
            v4: _v4,
            num: _num
        };
    };


    /**
     * Get all keys from object.
     * @name getObjectKeys
     * @memberof Utils#
     * @function
     * @param object {object}
     * @returns {Array}
     * @public
     */
    self.getObjectKeys = function (object) {
        var keys = [];
        for (var elem in object) {
            if (object.hasOwnProperty(elem)) {
                keys.push(elem);
            }
        }
        return keys;
    };

    /**
     * Complete the first level of an object with another.
     * @name completeFstLevelObj
     * @memberof Utils#
     * @function
     * @param target {object}
     * @param source {object}
     * @param overload {boolean} If true, properties of the target will be overloaded by source ones if they exist
     * @returns {object}
     * @public
     */
    self.completeFstLevelObj = function (target, source, overload) {
        if (target) {
            if (source) {
                for (var key in source) {
                    if (source.hasOwnProperty(key)) {
                        if (!target[key] || overload) {
                            target[key] = source[key];
                        }
                    }
                }
            }
        }
        else {
            target = source;
        }
        return target;
    };

    /**
     * Check if we are in Safari previewing case.
     * @name isPreview
     * @memberof Utils#
     * @function
     * @returns {boolean}
     * @public
     */
    self.isPreview = function () {
        return (window.navigator && window.navigator.loadPurpose === 'preview');
    };

    /**
     * Check if we are in Chrome or IE prerendering case.
     * @name isPrerender
     * @memberof Utils#
     * @function
     * @param callback {function}
     * @returns {boolean}
     * @public
     */
    self.isPrerender = function (callback) {
        var visibilityChangeEvent;
        var isPrerender = false;
        //Prefixes: Chrome, IE
        var prefixes = ['webkit', 'ms'];
        if (document.visibilityState === 'prerender') {
            //Opera 12.10 and Firefox 18 and later support
            visibilityChangeEvent = 'visibilitychange';
        }
        else {
            for (var i = 0; i < prefixes.length; i++) {
                if (document[prefixes[i] + 'VisibilityState'] === 'prerender') {
                    visibilityChangeEvent = prefixes[i] + 'visibilitychange';
                }
            }
        }
        if (typeof visibilityChangeEvent !== 'undefined') {
            var _manageCallback = function (event) {
                callback(event);
                self.removeEvtListener(document, visibilityChangeEvent, _manageCallback);
            };
            self.addEvtListener(document, visibilityChangeEvent, _manageCallback);
            isPrerender = true;
        }
        return isPrerender;
    };

    /**
     * Add an event listener to an object.
     * @name addEvtListener
     * @memberof Utils#
     * @function
     * @param obj {object} DOM Element on which you want to add the event listener
     * @param event {string} Event you need to listen
     * @param callback {function} When event is triggered, it takes one parameter which is the event object
     * @public
     */
    var _addEvtListener = self.addEvtListener = function (obj, event, callback) {
        if (obj.addEventListener) {
            obj.addEventListener(event, callback, false);
        } else if (obj.attachEvent) {
            obj.attachEvent('on' + event, callback);
        }
    };

    /**
     * Remove an event listener from an object.
     * @name removeEvtListener
     * @memberof Utils#
     * @function
     * @param obj {object} DOM Element on which you want to add the event listener
     * @param event {string} Event you need to listen
     * @param callback {function}
     * @public
     */
    self.removeEvtListener = function (obj, event, callback) {
        if (obj.removeEventListener) {
            obj.removeEventListener(event, callback, false);
        } else if (obj.detachEvent) {
            obj.detachEvent('on' + event, callback);
        }
    };

    /**
     * Load a script.
     * @name loadScript
     * @memberof Utils#
     * @function
     * @param scriptObj {object} Object containing script properties (url)
     * @param callback {function} Function to call on success or on error
     * @public
     */
    self.loadScript = function (scriptObj, callback) {
        var newScript;
        callback = callback || function () {};

        var errorCallback = function (event, a, b) {
            newScript.onload = newScript.onreadystatechange = newScript.onerror = null;
            callback({msg: 'script not loaded', event: event}); // first param not null means an error has occured
        };
        var onloadCallback = function (event) {
            event = event || window.event;
            // Check for different events in IEs
            if (event.type === "load" || (/loaded|complete/.test(newScript.readyState) && (!document.documentMode || document.documentMode < 9))) {
                newScript.onload = newScript.onreadystatechange = newScript.onerror = null;
                callback(null, event);
            }
        };

        newScript = document.createElement("script");
        newScript.type = "text/javascript";
        newScript.src = scriptObj.url;
        newScript.async = false;
        newScript.defer = false;
        newScript.onload = newScript.onreadystatechange = onloadCallback;
        newScript.onerror = errorCallback;
        var head = document.head || document.getElementsByTagName("head")[0];
        head.insertBefore(newScript, head.lastChild);
    };

    /**
     * Make a unique number with a given string.
     * @name hashcode
     * @memberof Utils#
     * @function
     * @param str
     * @returns {number}
     * @public
     */
    self.hashcode = function (str) {
        var hash = 0;
        if (str.length === 0) return hash;
        for (var i = 0; i < str.length; i++) {
            var character = str.charCodeAt(i);
            hash = ((hash << 5) - hash) + character;
            hash = hash & hash; // Convert to 32bit integer
        }
        return hash;
    };

    /**
     * Force document location.
     * @name setLocation
     * @memberof Utils#
     * @function
     * @param contextObj {object} Redirection's url and target (properties location & target)
     * @public
     */
    self.setLocation = function (contextObj) {
        var loc = contextObj['location'];
        var obj = window[contextObj['target']] || window;
        if (loc) {
            obj.location.href = loc;
        }
    };

    /**
     * Dispatch event for callbacks
     * @name dispatchCallbackEvent
     * @memberof Utils#
     * @function
     * @param name {string} Callback's name
     * @public
     */
    self.dispatchCallbackEvent = function (name) {
        // Create the event.
        var event = document.createEvent('Event');
        // Define that the event name is 'ATCallbackEvent'.
        event.initEvent('ATCallbackEvent', true, true);
        event.name = name;
        document.dispatchEvent(event);
    };

    /**
     * Add callback event
     * @name addCallbackEvent
     * @memberof Utils#
     * @function
     * @param func {function} function to execute
     * @public
     */
    self.addCallbackEvent = function (func) {
        // Create the event.
        var event = document.createEvent('Event');
        // Define that the event name is 'ATCallbackEvent'.
        event.initEvent('ATCallbackEvent', true, true);
        // Listen for the event.
        _addEvtListener(document, 'ATCallbackEvent', func);
    };
};

/**
 * Module with utility methods.
 * @name ATInternet.Utils
 * @memberof ATInternet
 * @type {Utils}
 * @public
 * @see {@link Utils}
 */
window['ATInternet']['Utils'] = new Utils();