Source: client/components/metadata/utils.js

/*globals ObjectRow,DataRelation,define,self,jsDataSet,jsDataQuery,metaModel,appMeta,_ */


/**
 * @module Utils
 * @description
 * Collection of utility functions
 */
(function (Deferred,OriginaDeferred) {


    /** Detect free variable `global` from Node.js. */
    let freeGlobal = typeof global === 'object' && global && global.Object === Object && global;
    /** Detect free variable `self`. */
    let freeSelf = typeof self === 'object' && self && self.Object === Object && self;
    /** Used as a reference to the global object. */
    let root = freeGlobal || freeSelf || Function('return this')();
    /** Detect free variable `exports`. */
    let freeExports = typeof exports === 'object' && exports && !exports.nodeType && exports;
    /** Detect free variable `module`. */
    let freeModule = freeExports && typeof module === 'object' && module && !module.nodeType && module;
    //noinspection JSUnresolvedVariable
    /** Detect free variable `global` from Node.js or Browserified code and use it as `root`. (thanks lodash)*/
    let moduleExports = freeModule && freeModule.exports === freeExports;

    let utils = {};

    /**
     * @function callOptAsync
     * @public
     * @description ASYNC
     * Calls a function fn that may have a callback parameter and returns a deferred value that will receive the result of fn
     * If fn has the parameter it is considered a callback that will receive the optional result as first parameter
     * If fn has no parameter it is considered a sincronous function its result is used  to fullfill the deferred.
     * If fn returns a deferred, its inner result is used to fullfill the result
     * The averall result is always a deferred value
     * @param {function} fn
     * @returns {Deferred}
     */
    utils.callOptAsync = function (fn) {
        let res = Deferred("utils.callOptAsync");
        if (fn.length > 0) {
            //fn has a parameter, it is considered a callback
            try {
                fn(function(resValue) {
                    res.resolve(resValue);
                });
            } catch (err) {
                return res.reject(err);
            }
        } else {
            setTimeout(function () {
                    try {
                        let result = fn();
                        if (result === null || result === undefined) {
                            res.resolve(result);
                            return;
                        }
                        //check if value is a deferred
                        if (typeof result.then === "function") {
                            result.then(function (innerResult) {
                                res.resolve(innerResult); //fullfill the result with the deferred result
                            }, function (error) {
                                res.reject(error);
                            });
                            return;
                        }
                        res.resolve(result);
                    } catch (err) {
                        if (err) console.log(err.message, err.stack);
                        res.reject(err);
                    }
                },
                0);
        }
        return res.promise();

    };

    /**
     * @function optionalDeferred
     * @public
     * @description ASYNC
     * Optionally executes a Deferred function, otherwise returns a deferred resolved with defaultValue
     * @param {boolean} condition
     * @param {function} func
     * @param {object} defaultValue
     * @returns {Deferred}
     */
    utils.optionalDeferred = function(condition, func, defaultValue) {
        if (!condition) return Deferred("utils.optionalDeferred").resolve(defaultValue).promise();
        return func();
    };

    /**
     * @function skipRun
     * @public
     * @description ASYNC
     * returns deferred function that accepts a parameter
     * @param {function} func
     * @returns {Deferred}
     */
    utils.skipRun = function(func) {
        return function(result) {
            let res = func(result);
            if (res.then) {
                return res.then(utils.fConst(result));
            }
            return OriginaDeferred().resolve(result).promise();
        };
    };

    /**
     * @function optBind
     * @public
     * @description SYNC
     * Returns function "fun" binded to "obj" or null if fun is null. Arguments can be provided
     * @param {function} fun  function to bind
     * @param {object} obj   object to use as "this"
     * @param {object} args  optional arguments
     * @returns {function}
     */
    utils.optBind = function(fun, obj, args) {
        if (!fun) return function() {};
        let rest = Array.prototype.slice.call(arguments, 1);
        if (rest.length > 1) return fun.bind.apply(fun, rest);
        return fun.bind(obj);
    };



    /**
     * @function fConst
     * @public
     * @description SYNC
     * Returns a constant function
     * @param {type} k
     */
    utils.fConst = function(k) {
        return function() { return k; };
    };

    /**
     * @function  sequence
     * @public
     * @description SYNC
     * This works like a $.When with optional async functions
     * @param {object} thisObject
     * @param {object[]} funArgs
     * @returns {function}
     */
    utils.sequence = function (thisObject, funArgs) {
        return OriginaDeferred.when(_.map(arguments,
            function (f, index) {
                if (index === 0) return true;
                return utils.callOptAsync(f);
            }));
    };

    /**
     * @function _if
     * @public
     * @description ASYNC
     * Builds an object chainable with these methods: .then().else().run() and eventually you can call .then() after run()
     * @param {boolean} condition
     * @returns {IfThenElse} {_if: function, _then:function, _else:function,run:function  }
     */
    utils._if  = function(condition) {
        let self = this;

        /**
         * Utility class to chain an if - then - else construct
         * @class IfThenElse
         * @constructor
         */
        function IfThenElse() {
            /**
             * @function _then
             * @param {function} then_clause
             * @return {IfThenElse}
             * @public
             */
            this._then = function(then_clause) {
                this._thenClause = then_clause;
                return this;
            };

            /**
             * @function _else
             * @param {function} else_clause
             * @return {Deferred}
             * @public
             */
            this._else = function(else_clause) {
                this._elseClause = else_clause;
                return this.run();
            };

            /**
             * @function run
             * @return {Deferred}
             */
            this.run = function() {
                if (condition) {
                    return self.asDeferred(this._thenClause());
                } else {
                    if (this._elseClause) {
                        return self.asDeferred(this._elseClause());
                    }
                }
                return self.asDeferred(undefined);
            };

            this.done = function(f) {
                return this.run().done(f);
            };

            this.then = function(doneFilter,failFilter,progressFilter) {
                return this.run().then(doneFilter,failFilter,progressFilter);
            };
        }

        return new IfThenElse();
    };

    /**
     * @function thenSequence
     * @public
     * @description ASYNC
     * Builds a chained function, chaining each the Deferred function with "then"
     * @param {Function[]} allDeferred.It is an array of function that must be return a deferred
     * @returns {Deferred}
     */
    utils.thenSequence = function(allDeferred) {
        // inizializzo primo elemento della catena di then
        let f = OriginaDeferred().resolve(true).promise();

        // concateno con then ogni deferred dell'array di input
        _.forEach(
            allDeferred,
            function(def) {
                f = f.then(def);
            });

        return f;
    };

    /**
     * @function filterArrayOnField
     * @public
     * @description SYNC
     * Returns the array of field value, taken from an object array, where field is not null or undefined
     * @param {object[]} arr
     * @param {string} field
     * @returns {object[]}
     */
    utils.filterArrayOnField = function(arr, field){
        return _.map(
            _.filter(arr, function(o) {
                return o[field];
            }), function (o) {
                return o[field];
            });
    };


    /**
     * @function asDeferred
     * @public
     * @description ASYNC
     * Evaluates the expression. if it is a deferred function then returns it, otherwise returns a Deferred
     * @param {Function} expression
     * @returns {Deferred}
     */
    utils.asDeferred = function(expression) {

        if (expression && typeof expression.then === "function") {
            return expression;
        }
        if (typeof expression === "function") {
            return new OriginaDeferred().resolve(expression).promise();
        }
        return new OriginaDeferred().resolve(expression).promise();
    };

    let univoqueId=0;

    /**
     * @method getUnivoqueId
     * @private
     * @description SYNC
     * Returns a progressive number. This number will be attached eventually to the id of the modal, to assure that each control is univoque.
     * @returns {number}
     */
    utils.getUnivoqueId = function () {
        univoqueId++;
        return univoqueId;
    };



    /**
     * @method isBrowserIE, this can only be invoked by frontend
     * @private
     * @description SYNC
     * Returns true if the browser is InternetExplorer
     * @returns {boolean}
     */
    utils.isBrowserIE = function () {
        try {
            if (typeof window === "undefined") {
                return  false;
            }
            let ua = window.navigator.userAgent;
            let msie = ua.indexOf("MSIE ");
            if (msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./)) return true;
            return false;
        } catch (e){
            return false;
        }
    };

    /**
     * Returns true if "str" is a valid url
     * @param {string} str
     * @returns {boolean}
     */
    utils.validURL = function(str) {
        let pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol
            '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name
            '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
            '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
            '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
            '(\\#[-a-z\\d_]*)?$','i'); // fragment locator
        return !!pattern.test(str);
    };

// Some AMD build optimizers like r.js check for condition patterns like the following:
    //noinspection JSUnresolvedVariable
    if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {
        // Expose lodash to the global object when an AMD loader is present to avoid
        // errors in cases where lodash is loaded by a script tag and not intended
        // as an AMD module. See http://requirejs.org/docs/errors.html#mismatch for
        // more details.
        root.utils = utils;

        // Define as an anonymous module so, through path mapping, it can be
        // referenced as the "underscore" module.
        //noinspection JSUnresolvedFunction
        define(function () {
            return utils;
        });
    }
    // Check for `exports` after `define` in case a build optimizer adds an `exports` object.
    else if (freeExports && freeModule) {
        if (moduleExports) { // Export for Node.js or RingoJS.
            (freeModule.exports = utils).utils = utils;
        }
        else { // Export for Narwhal or Rhino -require.
            freeExports.utils = utils;
        }
    }
    else {
        // Export for a browser or Rhino.
        if (root.appMeta){
            root.appMeta.utils = utils;
        }
        else {
            root.utils=utils;
        }

    }

}(  (typeof appMeta === 'undefined') ? require('./EventManager').Deferred : appMeta.Deferred,
    (typeof $ === 'undefined')? require('JQDeferred'): $.Deferred
));