www.openair.com Open in urlscan Pro
129.159.101.111  Public Scan

Submitted URL: http://coupa.app.openair.com/version/cb83.42bf/js/OA.lib.lang.js
Effective URL: https://www.openair.com/js/OA.lib.lang.js
Submission: On April 22 via manual from CH — Scanned from DE

Form analysis 0 forms found in the DOM

Text Content

/*eslint no-unused-vars: 0*/
/*eslint oa-require-jsdoc: [0] */

/**
 * Defined namespaces:
 * OA.lib.lang
 */

/* use ECMAScript 5 Strict mode */
(function() {
    "use strict";
}());

/**
 * This namespace defines JS language extensions.
 * All function like generating object ID, array
 * extensions and other are stored here.
 */

/* define namespace */
OA.namespace('OA.lib.lang');

/* stores general object ID */
OA.lib.lang.oid = 0;

OA.lib.lang.escapeEntities = {
    '&': '&',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#x27;'
};

OA.lib.lang.tagWhitelist = [
    'BR',
    'I',
    'B',
    'U'
];

OA.lib.lang.XSSLevels = {
    AGGRESSIVE: 1,
    HIGH: 5,
    MODERATE: 10,
    LOW: 20,
    NONE: 100
};

/* object with currency format definition */
OA.lib.lang.currency = {
    // supported formats
    formats: {
        // $25.5
        '%symbol%value': ['USD', 'CAD', 'AUD', 'HKD', 'ZAR', 'KRW', 'NAD', 'SGD', 'NZD', 'GBP', 'JPY', 'CNY', 'RMB', 'EUR'],
        // 25.5 $
        '%value%separator%symbol': ['FRF', 'RUP', 'INR', 'ESP', 'DEM', 'SEK', 'FIM', 'DKK'],
        // $ 25.5
        '%symbol%separator%value': ['CHF', 'ITL', 'NLG', 'AWG', 'PHP', 'NOK']
    },
    // default format (USD 25.5)
    defaultFormat: '%currency%separator%value',
    // separator between currency and value
    separator: '&nbsp;',
    // cached currency formats, see OA.lib.lang.formatCurrency
    cache: undefined
};

/**
 * Generates specific ID for object that
 * needs that behavior (calls this function).
 */
OA.lib.lang.generateId = function() {
    return OA.lib.lang.oid++;
};

/**
 * Sugarize JS language
 * This function runs couple of functions that
 * extend JS internal objects like Function,
 * Object, Image ...
 */
OA.lib.lang.sugar = function() {

    /**
     * Extend every object with oid function
     * Objects are then able to retrieve
     * their specific id within whole OA app
     *
     * TODO: for some reason this extension causes jQuery crashes
     *       we need to test it!
     */
    //Object.prototype.oid = function () {
    //    var newOid = OA.lib.lang.generateId();
    //
    //    /* *
    //     * Rewrite instance of oid function for
    //     * object that called oid() function
    //     * to always return it's specific oid
    //     */
    //    this.oid = function () {
    //        return newOid;
    //    };
    //
    //    /* return specific object id (oid) */
    //    return newOid;
    //};

    (function() {
        $.fn.bounds = function() {
            var bounds = {
                left: Number.POSITIVE_INFINITY,
                top: Number.POSITIVE_INFINITY,
                right: Number.NEGATIVE_INFINITY,
                bottom: Number.NEGATIVE_INFINITY,
                width: Number.NaN,
                height: Number.NaN
            };

            this.each(function(i, el) {
                var elQ, off;
                elQ = $(el);
                off = elQ.offset();
                off.right = off.left + $(elQ).width();
                off.bottom = off.top + $(elQ).height();

                if (off.left < bounds.left) {
                    bounds.left = off.left;
                }

                if (off.top < bounds.top) {
                    bounds.top = off.top;
                }

                if (off.right > bounds.right) {
                    bounds.right = off.right;
                }

                if (off.bottom > bounds.bottom) {
                    bounds.bottom = off.bottom;
                }
            });

            bounds.width = bounds.right - bounds.left;
            bounds.height = bounds.bottom - bounds.top;
            return bounds;
        };
    })();

    (function() {
        $.fn.getNonColSpanIndex = function() {
            var allCells, normalIndex, nonColSpanIndex;

            if (!$(this).is('td') && !$(this).is('th')) {
                return -1;
            }

            allCells = this.parent('tr').children();
            normalIndex = allCells.index(this);
            nonColSpanIndex = 0;

            allCells.each(function(i) {
                var colspan = $(this).attr('colspan');
                if (parseInt(i, 10) === parseInt(normalIndex, 10)) {
                    return false;
                }

                colspan = colspan ? parseInt(colspan, 10) : 1;
                nonColSpanIndex += colspan;
            });

            return nonColSpanIndex;
        };
    })();

    /* Adds scrollstart and scrollstop event */
    (function() {

        var special = $.event.special,
            uid1 = 'D' + (+new Date()),
            uid2 = 'D' + (+new Date() + 1);

        special.scrollstart = {
            setup: function() {

                var timer,
                    handler = function(evt) {

                        var _self = this,
                            _args = arguments;

                        if (timer) {
                            clearTimeout(timer);
                        } else {
                            evt.type = 'scrollstart';
                            $.event.handle.apply(_self, _args);
                        }

                        timer = setTimeout(function() {
                            timer = null;
                        }, special.scrollstop.latency);

                    };

                $(this).on('scroll', handler).data(uid1, handler);

            },
            teardown: function() {
                $(this).off('scroll', $(this).data(uid1));
            }
        };

        special.scrollstop = {
            latency: 300,
            setup: function() {

                var timer,
                    handler = function(evt) {

                        var _self = this,
                            _args = arguments;

                        if (timer) {
                            clearTimeout(timer);
                        }

                        timer = setTimeout(function() {

                            timer = null;
                            evt.type = 'scrollstop';
                            $.event.handle.apply(_self, _args);

                        }, special.scrollstop.latency);

                    };

                $(this).on('scroll', handler).data(uid2, handler);

            },
            teardown: function() {
                $(this).off('scroll', $(this).data(uid2));
            }
        };

    })();
};

OA.lib.lang.sameStructure = function(objA, objB) {
    var aKeys, bKeys;
    if (typeof objA === 'object' && typeof objB === 'object') {
        aKeys = Object.keys(objA).sort();
        bKeys = Object.keys(objB).sort();
        return JSON.stringify(aKeys) === JSON.stringify(bKeys);
    } else {
        return objA === objB;
    }
};

/**
 * JSON method to clone any object
 * @param  {Object} obj template object
 * @return {Object}     cloned object
 */
OA.lib.lang.cloneObject = function(obj) {
    return JSON.parse(JSON.stringify(obj));
};

OA.lib.lang.collect = function() {
    var ret = {},
        len = arguments.lenght,
        i = 0,
        p;
    for (i = 0; i < len; i++) {
        for (p in arguments[i]) {
            if (arguments[i].hasOwnProperty(p)) {
                ret[p] = arguments[i][p];
            }
        }
    }
    return ret;
};

/**
 * Convert value to JSON string
 */
OA.lib.lang.stringify = function(value, replacer, space) {
    return (JSON.stringify(value, replacer, space));
};

/**
 * Convert json string to object
 */
OA.lib.lang.destringify = function(text, reviver) {
    return (JSON.parse(text, reviver));
};

OA.lib.lang.capitaliseFirstLetter = function(string) {
    return string.charAt(0).toUpperCase() + string.slice(1);
};

/**
 * Return value with 'px' suffix
 *
 * @param  {String}     value  value as string
 *
 * @return {String}     value + 'px'
 */
OA.lib.lang.inPx = function(value) {
    return value + 'px';
};

/**
 * Returns string prefixed with '.'
 *
 * @param  {String}     string  class name
 *
 * @example     OA.lib.lang.stringAsClassSelector('hover') => '.hover'
 *
 * @return {String}     '.' + class name
 */
OA.lib.lang.stringAsClassSelector = function(string) {
    return '.' + string;
};

/**
 * Get array of classes of passed element
 *
 * @param   {object}    element     DOM node
 * @return  {Array}     array of strings of classnames
 */
OA.lib.lang.getClassList = function(element) {
    return (element && element.className) ? element.className.split(/\s+/) : [];
};

/**
 * fillprop - overwrite @defparams properties in object by same property values from @params
 *
 * var def = OA.fillprop({ test : true }, { test : false, foo : "" } ) ;
 * console.log(def)
 * { test : false }
 *
 * @defparams  {Object} default values
 * @params  {Object} values to overwrite default values
 * @return {Object}
 */
OA.fillprop = function(defparams, params) {
    var attr, retval = {};
    if (params === undefined) {
        params = {};
    }
    //Join parameters
    for (attr in defparams) {
        if (params[attr] !== undefined) {
            retval[attr] = params[attr];
        } else {
            retval[attr] = defparams[attr];
        }
    }
    return retval;
};

/**
 * cleanPage get body of page from full html page
 * @return String
 */
OA.cleanPage = function(msg) {
    var patt;
    msg = msg.slice(msg.search('<body'), msg.search('</body'));
    patt = new RegExp("<body[^>]*>", "");
    msg = msg.replace(patt, "");
    return (msg);
};

/**
 * Escapes a string for insertion into HTML
 *
 * OA.escape('James, Eric & Tony');
 * result: "James, Eric &amp; Tony"
 *
 * @param {string} str string to escape
 * @return {string}
 */
OA.lib.lang.escape = function(s, forAttribute) {
    return s.toString().replace(forAttribute ? /[&<>'"]/g : /[&<>]/g, function(c) {
        return OA.lib.lang.escapeEntities[c];
    });
};

/**
 * Strips HTML tags like <script> from string.
 * https://stackoverflow.com/questions/6659351/removing-all-script-tags-from-html-with-js-regular-expression
 *
 * @param {String} s
 *
 * @return {String}
 */
OA.lib.lang.stripTags = function(s) {
    var i;
    var el;
    var div = document.createElement('div');
    var re;
    var output;
    var children;
    var toRemove = [];

    // process stripping only for AGGRESSIVE or HIGH level
    if (!s || OA.core.XSSLevel > OA.lib.lang.XSSLevels.HIGH) {
        return s;
    }

    div.innerHTML = OA.lib.lang.stripAttrs(s);
    children = div.querySelectorAll('*');

    for (i = 0; i < children.length; i++) {
        el = children[i];

        // element should be removed
        if (OA.lib.lang.tagWhitelist.indexOf(el.tagName) === -1) {
            // collect elements to be removed (cannot be removed directly in for loop - changing index)
            toRemove.push(el);
            // allowed tag, remove all attributes (no need to have any attributes
            // for 'b', 'i', 'u' and 'br')
        } else {
            while (el.attributes.length > 0) {
                el.removeAttribute(el.attributes[0].name);
            }
        }
    }

    if (toRemove.length) {
        for (i = 0; i < toRemove.length; i++) {
            toRemove[i].parentNode.removeChild(toRemove[i]);
        }
    }

    output = div.innerHTML;

    // replace entities (created due to using innerHTML)
    for (i in OA.lib.lang.escapeEntities) {
        re = new RegExp(OA.lib.lang.escapeEntities[i], 'g');
        output = output.replace(re, i);
    }

    return output;
};

/**
 * Strips on* HTML attributes from string
 * e.g. onload, onerror, onfocus ...
 *
 * @param {String} s
 *
 * @return {String}
 */
OA.lib.lang.stripAttrs = function(s) {
    if (!s) {
        return s;
    }

    // onerror attribute must be stripped before adding into element
    // othwerwise onerror will be triggered (e.g. <img src=x onerror=alert(1)>)
    // with apos and quotes
    s = s.replace(new RegExp("on[a-z]+='.*?'", 'ig'), '');
    s = s.replace(new RegExp('on[a-z]+=".*?"', 'ig'), '');
    // attribute value without quotes
    s = s.replace(new RegExp('on[a-z]+=[^> ]+([> ])', 'ig'), '$1');

    return s;
};

/**
 * Returns the value of s with the first character uppercased.
 *
 * OA.ucfirst('james');
 * result: "James"
 *
 * @param {string} s string to uppercase
 * @return {string}
 */
OA.lib.lang.ucfirst = function(s) {
    return (typeof s === "string" && s.length > 0 ? s.charAt(0).toUpperCase() + s.substr(1) : s);
};

/**
 * Implementation of MurmurHash3
 *
 * @author Gary Court
 * @see http://github.com/garycourt/murmurhash-js
 * @author Austin Appleby
 * @see http://sites.google.com/site/murmurhash/
 *
 * @param {String} key - ASCII only
 * @param {Number} seed - positive integer only
 *
 * @return {Number} 32-bit positive integer hash
 */
OA.lib.lang.computeHash = function(key, seed) {
    var remainder, bytes, h1, h1b, c1, c1b, c2, c2b, k1, i;

    remainder = key.length & 3;
    bytes = key.length - remainder;
    h1 = seed;
    c1 = 0xcc9e2d51;
    c2 = 0x1b873593;
    i = 0;

    while (i < bytes) {
        k1 =
            ((key.charCodeAt(i) & 0xff)) |
            ((key.charCodeAt(++i) & 0xff) << 8) |
            ((key.charCodeAt(++i) & 0xff) << 16) |
            ((key.charCodeAt(++i) & 0xff) << 24);

        ++i;

        k1 = ((((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16))) & 0xffffffff;
        k1 = (k1 << 15) | (k1 >>> 17);
        k1 = ((((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16))) & 0xffffffff;

        h1 ^= k1;
        h1 = (h1 << 13) | (h1 >>> 19);
        h1b = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16))) & 0xffffffff;
        h1 = (((h1b & 0xffff) + 0x6b64) + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16));
    }

    k1 = 0;

    switch (remainder) {
        case 3:
            k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16;
            /* falls through */
        case 2:
            k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8;
            /* falls through */
        case 1:
            k1 ^= (key.charCodeAt(i) & 0xff);

            k1 = (((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff;
            k1 = (k1 << 15) | (k1 >>> 17);
            k1 = (((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff;
            h1 ^= k1;
    }

    h1 ^= key.length;

    h1 ^= h1 >>> 16;
    h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff;
    h1 ^= h1 >>> 13;
    h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff;
    h1 ^= h1 >>> 16;

    return h1 >>> 0;
};

/**
 * Return value with currency symbol.
 * @param {String|Number} value formatted value
 * @param {String} currency currency abbreviation
 * @param {String} symbol currency symbol
 *
 * @return {String}
 */
OA.lib.lang.formatCurrency = function(value, currency, symbol) {
    var format;
    var i;
    var currencies;
    var selectedFormat;
    var result;

    if (_.isUndefined(value)) {
        throw "Value is required.";
    }
    if (_.isUndefined(currency)) {
        throw "Currency is required.";
    }

    // fallback symbol is same as currency
    if (_.isUndefined(symbol)) {
        symbol = currency;
    }

    // cache formats by currencies
    if (_.isUndefined(OA.lib.lang.currency.cache)) {
        OA.lib.lang.currency.cache = {};

        // process all formats
        for (format in OA.lib.lang.currency.formats) {
            currencies = OA.lib.lang.currency.formats[format];

            // add format for all currencies
            for (i = 0; i < currencies.length; i++) {
                OA.lib.lang.currency.cache[currencies[i]] = format;
            }
        }
    }

    // try to find format for specified currency
    if (OA.lib.lang.currency.cache[currency]) {
        selectedFormat = OA.lib.lang.currency.cache[currency];
    }

    // use default format if specific format for currency not found
    if (!selectedFormat) {
        selectedFormat = OA.lib.lang.currency.defaultFormat;
    }

    // replace all wildcards
    result = selectedFormat.replace('%value', value);
    result = result.replace('%separator', OA.lib.lang.currency.separator);
    result = result.replace('%symbol', symbol);
    result = result.replace('%currency', currency);

    return result;
};

/**
 * Call handler for clearing account translations, provided the URL is defined.
 *
 * @return {boolean}
 */
OA.lib.lang.clearLanguage = function() {
    var jqXHR;
    var clearTranslationsURL = OA.core.clearTranslationsURL;
    if (_.isUndefined(clearTranslationsURL)) {
        return false;
    }
    jqXHR = $.ajax({
        type: "GET",
        url: clearTranslationsURL,
        dataType: "json"
    });
    jqXHR.done(function(response) {
        OA.publish('OA.comp.Notification.showTimeout', ['Account translations were successfully cleared: ' + response.rowsDeleted + ' row(s) deleted', 'Success']);
    });
    jqXHR.fail(function() {
        OA.publish('OA.comp.Notification.showTimeout', ['Could not clear account translations', 'Error']);
    });

    return true;
};