Source: Utils.js

/**
 * Utils module
 * @module src/modules/Utils
 * @type {Object}
 */
/* globals ActiveXObject */
(function(stargateModules){
    /**
     * @class
     * @alias module:src/modules/Utils.Logger
     * @param {String} label - OFF|DEBUG|INFO|WARN|ERROR|ALL
     * @param {String} tag - a tag to identify a log group. it will be prepended to any log function
     * @param {Object} [styles={background:"white",color:"black"}] -
     * @param {String} styles.background - background color CSS compatibile
     * @param {String} styles.color - color text CSS compatible
     * @example
     * var myLogger = new Logger("ALL", "TAG",{background:"black",color:"blue"});
     * myLogger.i("Somenthing", 1); // output will be > ["TAG"], "Somenthing", 1
     * myLogger.setLevel("off") // other values OFF|DEBUG|INFO|WARN|ERROR|ALL
     * */
    function Logger(label, tag, styles){
        this.level = Logger.levels[label.toUpperCase()];
        this.styles = styles || {background:"white",color:"black"}; //default
        this.tag = "%c " + tag + " ";
        this.isstaging = ("IS_STAGING = 1".slice(-1) === "1");

        this.styleString = "background:" + this.styles.background + ";" + "color:" + this.styles.color + ";";
        
        var argsToString = function() {
            if (arguments.length < 1) {
                return "";
            }
            var args = Array.prototype.slice.call(arguments[0]);
            var result = '';
            for (var i=0; i<args.length; i++) {
                if (typeof (args[i]) === 'object') {
                    result += " " + JSON.stringify(args[i]);
                }
                else {
                    result += " " + args[i];
                }
            }
            return result;
        };
        
        var consoleLog = window.console.log.bind(window.console, this.tag, this.styleString);
        var consoleInfo = window.console.info.bind(window.console, this.tag, this.styleString);
        var consoleError = window.console.error.bind(window.console, this.tag, this.styleString);
        var consoleWarn = window.console.warn.bind(window.console, this.tag, this.styleString);
        
        if (!this.isstaging) {
            consoleLog = function(){
                window.console.log("[D] [Stargate] "+argsToString.apply(null, arguments));
            };
            consoleInfo = function(){
                window.console.log("[I] [Stargate] "+argsToString.apply(null, arguments));
            };
            consoleError = function(){
                window.console.log("[E] [Stargate] "+argsToString.apply(null, arguments));
            };
            consoleWarn = function(){
                window.console.log("[W] [Stargate] "+argsToString.apply(null, arguments));
            };
        }
        //private and immutable
        Object.defineProperties(this, {
            "__d": {
                value: consoleLog,
                writable: false,
                enumerable:false,
                configurable:false
            },
            "__i": {
                value: consoleInfo,
                writable: false,
                enumerable:false,
                configurable:false
            },
            "__e": {
                value: consoleError,
                writable: false,
                enumerable:false,
                configurable:false
            },
            "__w": {
                value: consoleWarn,
                writable: false,
                enumerable:false,
                configurable:false
            }
        });
    }

    //Logger.prototype.group
    //OFF < DEBUG < INFO < WARN < ERROR < ALL
    // 0  < 1  < 2 < 3 < 4 < 5
    Logger.levels = {
        ALL:5,
        ERROR:4,
        WARN:3,
        INFO:2,
        DEBUG:1,
        OFF:0
    };

    /**
     * Error Logging
     * @param {*} [arguments]
     * */
    Logger.prototype.e = function(){

        if(this.level !== 0 && this.level >= Logger.levels.ERROR){
            this.__e(arguments);
        }
    };

    /**
     * Info Logging
     * @param {*} [arguments]
     * */
    Logger.prototype.i = function(){

        if(this.level !== 0 && this.level >= Logger.levels.WARN){
            this.__i(arguments);
        }
    };

    /**
     * Warn Logging
     * @param {*} [arguments]
     * */
    Logger.prototype.w = function(){
        if(this.level !== 0 && this.level >= Logger.levels.INFO){
            this.__w(arguments);
        }
    };

    /**
     * Debug Logging
     * @param {*} [arguments]
     * */
    Logger.prototype.d = function(){

        if(this.level !== 0 && this.level >= Logger.levels.DEBUG){
            this.__d(arguments);
        }
    };

    /**
     * Set the level of the logger
     * @param {String} label - OFF|DEBUG|INFO|WARN|ERROR|ALL
     * */
    Logger.prototype.setLevel = function(label){
        this.level = Logger.levels[label];
    };

    /**
     * Iterator
     *
     * @alias module:src/modules/Utils.Iterator
     * @example
     * var myArray = ["pippo", "pluto", "paperino"];
     * var it = Utils.Iterator(myArray);
     * it.next().value === "pippo"; //true
     * it.next().value === "pluto"; //true
     * it.next(true).value === "paperino" //false because with true you can reset it!
     * @param {Array} array - the array you want to transform in iterator
     * @returns {Object} - an iterator-like object
     * */
    function Iterator(array){
        var nextIndex = 0;

        return {
            next: function(reset){
                if(reset){nextIndex = 0;}
                return nextIndex < array.length ?
                {value: array[nextIndex++], done: false} :
                {done: true};
            }
        };
    }

    /**
     * A function to compose query string
     *
     * @alias module:src/modules/Utils.composeApiString
     * @example
     * var API = "http://jsonplaceholder.typicode.com/comments"
     * var url = composeApiString(API, {postId:1});
     * // url will be "http://jsonplaceholder.typicode.com/comments?postId=1"
     * @param {Strinq} api
     * @param {Object} params - a key value object: will be append to <api>?key=value&key2=value2
     * @returns {String} the string composed
     * */
    function composeApiString(api, params){
        api += "?";
        var qs = "";

        for(var key in params){
            qs += encodeURIComponent(key) + "=" + encodeURIComponent(params[key]) + "&";
        }

        if (qs.length > 0){
            qs = qs.substring(0, qs.length-1); //chop off last "&"
        }
        return api + qs;
    }

    /**
     * getJSON
     *
     * @alias module:src/modules/Utils.getJSON
     * @param {String} url - for example http://jsonplaceholder.typicode.com/comments?postId=1
     * @returns {Promise<Object|String>} the string error is the statuscode
     * */
    function getJSON(url){
        url = encodeURI(url);
        var xhr = typeof XMLHttpRequest != 'undefined' ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');

        var responseTypeAware = 'responseType' in xhr;

        xhr.open("GET", url, true);
        if (responseTypeAware) {
            xhr.responseType = 'json';
        }

        var daRequest = new Promise(function(resolve, reject){
            xhr.onreadystatechange = function(){
                if (xhr.readyState === 4) {
                    try{
                        var result = responseTypeAware ? xhr.response : JSON.parse(xhr.responseText);
                        resolve(result);
                    }catch(e){
                        reject(e);
                    }
                }
            };
        });

        xhr.setRequestHeader('Content-type', 'application/json; charset=UTF-8');
        xhr.send();
        return daRequest;
    }

    /**
     * Make a jsonp request, remember only GET
     * The function create a tag script and append a callback param in querystring.
     * The promise will be reject after 3s if the url fail to respond
     *
     * @class
     * @alias module:src/modules/Utils.jsonpRequest
     * @example
     * request = new jsonpRequest("http://www.someapi.com/asd?somequery=1");
     * request.then(...)
     * @param {String} url - the url with querystring but without &callback at the end or &function
     * @returns {Promise<Object|String>}
     * */
    function jsonpRequest(url){
        var self = this;
        self.timeout = 3000;
        self.called = false;
        if(window.document) {
            var ts = Date.now();
            self.scriptTag = window.document.createElement("script");
            url += "&callback=window.__jsonpHandler_" + ts;
            self.scriptTag.src = url;
            self.scriptTag.type = 'text/javascript';
            self.scriptTag.async = true;

            self.prom = new Promise(function(resolve, reject){
                var functionName = "__jsonpHandler_" + ts;
                window[functionName] = function(data){
                    self.called = true;
                    resolve(data);
                    self.scriptTag.parentElement.removeChild(self.scriptTag);
                    delete window[functionName];
                };
                //reject after a timeout
                setTimeout(function(){
                    if(!self.called){
                        reject("Timeout jsonp request " + ts);
                        self.scriptTag.parentElement.removeChild(self.scriptTag);
                        delete window[functionName];
                    }
                }, self.timeout);
            });
            // the append start the call
            window.document.getElementsByTagName("head")[0].appendChild(self.scriptTag);
            //return self.daPromise;
        }
    }

    /**
     * getImageRaw from a specific url
     *
     * @alias module:src/modules/Utils.getImageRaw
     * @param {Object} options - the options object
     * @param {String} options.url - http or whatever
     * @param {String} [options.responseType="blob"] - possible values arraybuffer|blob
     * @param {String} [options.mimeType="image/jpeg"] - possible values "image/png"|"image/jpeg" used only if "blob" is set as responseType
     * @param {Boolean} options.withCredentials - set with credentials before send
     * @param {Function} [_onProgress=function(){}]
     * @returns {Promise<Blob|ArrayBuffer|Error>}
     */
    function getImageRaw(options, _onProgress){
        var onProgress = _onProgress || function(){};
        return new Promise(function(resolve, reject){
            var request = new XMLHttpRequest();
            request.open ("GET", options.url, true);
            request.responseType = options.responseType || "blob";
            
            if(options.withCredentials){
               request.withCredentials = options.withCredentials; 
            }
                        
            function transferComplete(){
                var result;
                switch(options.responseType){
                    case "blob":
                        result = new Blob([this.response], {type: options.mimeType || "image/jpeg"});
                        break;
                    case "arraybuffer":
                        result = this.response;
                        break;
                    default:
                        result = this.response;
                        resolve(result);
                        break;

                }
            }

            var transferCanceled = reject;
            var transferFailed = reject;

            request.addEventListener("progress", onProgress, false);
            request.addEventListener("load", transferComplete, false);
            request.addEventListener("error", transferFailed, false);
            request.addEventListener("abort", transferCanceled, false);

            request.send(null);
        });

    }

    /**
     * extend: this function merge two objects in a new one with the properties of both
     *
     * @param {Object} o1 -
     * @param {Object} o2 -
     * @returns {Object} a brand new object results of the merging
     * */
    function extend(o1, o2){

        var isObject = Object.prototype.toString.apply({});
        if((o1.toString() !== isObject) || (o2.toString() !== isObject)) {
            throw new Error("Cannot merge different type");
        }
        var newObject = {};
        for (var k in o1){
            if(o1.hasOwnProperty(k)){
                newObject[k] = o1[k];
            }
        }

        for (var j in o2) {
            if(o2.hasOwnProperty(k)){
                newObject[j] = o2[j];
            }
        }
        return newObject;
    }

    var exp = {
        Iterator:Iterator,
        Logger:Logger,
        composeApiString:composeApiString,
        getJSON:getJSON,
        jsonpRequest:jsonpRequest,
        getImageRaw:getImageRaw,
        extend:extend
    };

    if(stargateModules){
        stargateModules.Utils = exp;
    }else{
        window.Utils = exp;
    }

})(stargateModules);