JsUtils

JavaScript Utility functions

目前为 2016-04-25 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/19117/121891/JsUtils.js

/*
 ____________________
< What amazing code! >
 --------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
*/
/**
 * Some helpful functions I made. all in SEF to make sure object scope is kept.
 * 
 * All objects are static
 **/

/**********************************/
/*  JQUERY FUNCTIONS
/**********************************/
"use strict";
(function ($) { // lets pass this jQuery so we know nothing else has messed with it
    /**
     * Add functions to jQuery
     */
    $.fn.extend({
        /**
         * Sort a given list.
         * You may pass in any amount of {String} parameters as you like, passing these in will be ingored by the sorting method.
         * The first parameter MUST be a boolean. this specifies if you want the ignored elements to be at the bottom of the list (top by default)
         * 
         * 
         * for example. sort a list, but ignore "Logout":
         * $("#myList").sortList(false,"Logout");
         * This will make "Logout" appear first in the list as it has been ignored by the sort function.
         * 
         * to make logout appear last:
         * $("#myList").sortList(true,"Logout");
         * 
         * Arguments are optinal and do not need to be supplied (to sort the whole list)
         * 
         * parameters:
         * {boolean} pushToBottom
         * {String} elements to ignore
         */
        sortList: function () {
            return function () {
                if (this.length === 0) {
                    return;
                }
                if (!this.is("ul, ol")) {
                    throw "Error: sortList can only be used on a ul or ol tag!";
                }
                var args = arguments;
                var pushToBottom = false;

                if (typeof args[0] !== "undefined") {
                    if (typeof args[0] !== "boolean") {
                        throw "the first argument must be a boolean";
                    }
                    pushToBottom = args[0];
                }

                var excludedResults = []; // keep array of excluded li elements

                for (var x = 0; x < this.length; x++) {
                    var currentList = $(this.get(x));

                    var listitems = currentList.children('li').not(function (i, e) {
                        for (var i = 1; i < args.length; i++) {
                            var currentArg = args[i];
                            if ($.trim($(this).text()) === currentArg) {
                                excludedResults.push($(this));
                                return true;
                            } else {
                                continue;
                            }
                        }
                        return false;
                    }).get();
                    listitems.sort(function (a, b) {
                        return $(a).text().toUpperCase().localeCompare($(b).text().toUpperCase());
                    });
                    $.each(listitems, function (k, v) {
                        currentList.append(v);
                    });
                    if (pushToBottom === true) {
                        $.each(excludedResults, function (k, v) {
                            $(this).parent().append(this);
                        });
                    }
                }
                return this;
            }.apply(this, arguments);
        },

        sortSelect: function () {
            if (!this.is("select")) {
                throw "SortSelect can only be used on a select node";
            }

            var ignore = [];
            $.each(this.children(), function (k, v) {
                if (typeof $(this).data("sort-ignore") === "string") {
                    ignore.push(this);
                }
            })

            var options = this.children("option").not(function (el) {
                if ($.inArray(this, ignore) >= 0) {
                    return true;
                }
                return false;
            });
            options.sort(function (a, b) {
                return $(a).text().toUpperCase().localeCompare($(b).text().toUpperCase());
            });

            for (var i = 0; i < options.length; i++) {
                this.append(options[i]);
            }

            return this;
        },

        center: function () {
            this.css("position", "absolute");
            this.css("top", Math.max(0, (($(window).height() - $(this).outerHeight()) / 2) + $(window).scrollTop()) + "px");
            this.css("left", Math.max(0, (($(window).width() - $(this).outerWidth()) / 2) + $(window).scrollLeft()) + "px");
            return this;
        },

        /**
         * Add sortTable function to jQuery
         * Using jQuery for this is highly inefficient, but I do not know any pure JS to do the same thing.
         * to use do $("#myTable").sortTable(1);
         */
        sortTable: function (col) {
            typeof col === ("undefined") ? col = -1 : col = col;
            if (this.length === 0) {
                return;
            }
            if (typeof col !== "number") {
                throw "col must be of type number. Got type" + typeof col + " instead";
            }
            if (!this.is("table")) {
                throw "Error: sortList can only be used on a table!";
            }

            var rows = $(this).find("tbody").children('tr').get();;
            if (col === -1) {
                rows.sort(function (a, b) {
                    return $(a).text().toUpperCase().localeCompare($(b).text().toUpperCase());
                });
            } else {
                rows.sort(function (a, b) {
                    var textA = $(a).children('td').eq(col).text().toUpperCase();
                    var textB = $(b).children('td').eq(col).text().toUpperCase();
                    return textA.localeCompare(textB);
                });
            }
            for (var i = 0; i < rows.length; i++) {
                $(this).find("tbody").append(rows[i]); // .append() will move them for you
            }
            return this;
        },

        /**
         * Will change the text of anything passed in, even if the node is a text node
         */
        changetext: function (text) {
            if (this.length === 0) {
                return;
            }
            this.contents().filter(function () {
                return this.nodeType === 3;
            })[0].nodeValue = text;

            return this;
        },

        /**
         * Enables a given anchor or button depending on the specified parameter.
         * If true is specified then the button / anchor is enabled, otherwise the button / anchor is disabled
         */
        enableButton: function (enable) {
            if (this.length === 0) {
                return;
            }

            if (!this.is("a, button")) {
                throw "This function may only be used on a button or anchor tag";
            }


            if (typeof enable === "undefined" || enable === null) {
                throw "The argument passed to this function must be a boolean";
            }
            if (enable === true) {
                this.prop('disabled', false).removeClass("disabled");
            } else {
                this.prop('disabled', true).addClass("disabled");
            }

            return this;
        }
    });
    overrideMethods();
    customFilters();
    customEvents();

    function overrideMethods() {
        hide();
        show();

        function hide() {
            var originalHideMethod = jQuery.fn.hide;

            $.fn.hide = function () {
                originalHideMethod.apply(this, arguments);

                if (!this.is(":hidden")) {
                    this.addClass("hide");
                }
                return this;
            };
        }

        /**
         * Because bootstrap's 3 hide class has an important in the display (display: none !important), any attempt to call the native "show()" in jquery will fail to show the tag
         * This will show the tag using the native function, if it is still hidden, it will strip the hide class off the element and apply inline css
         */
        function show() {
            var originalShowMethod = jQuery.fn.show;

            $.fn.show = function () {
                originalShowMethod.apply(this, arguments);

                var type = getElementDefaultDisplay(this.prop("nodeName"));
                if (this.is(":hidden")) { // if still hidden, then bootstrap's hide class was used
                    this.removeClass("hide").css("display", type);
                }
                return this;
            };
        }

        /**
         * Get the default style of a tag (div = block, span = inline, etc...)
         * @param   {String} tag Tag name
         * @returns {String} Default style
         */
        function getElementDefaultDisplay(tag) {
            var cStyle;
            var t = document.createElement(tag);
            var gcs = "getComputedStyle" in window;

            document.body.appendChild(t);
            cStyle = (gcs ? window.getComputedStyle(t, "") : t.currentStyle).display;
            document.body.removeChild(t);

            return cStyle;
        }
    }

    function customFilters() {
        offScreen();

        function offScreen() {
            Object.defineProperty(jQuery.expr.filters, "offscreen", {
                value: function (_el) {
                    var el = $(_el);
                    var win = $(window);

                    var viewport = {
                        top: win.scrollTop(),
                        left: win.scrollLeft()
                    };
                    viewport.right = viewport.left + win.width();
                    viewport.bottom = viewport.top + win.height();

                    var bounds = el.offset();
                    bounds.right = bounds.left + el.outerWidth();
                    bounds.bottom = bounds.top + el.outerHeight();

                    return (viewport.right < bounds.left || viewport.left > bounds.right || viewport.bottom < bounds.top || viewport.top > bounds.bottom);
                }
            })
        }
    }

    function customEvents() {
        classChanged();

        function classChanged() {
            var originalAddClassMethod = jQuery.fn.addClass;

            jQuery.fn.addClass = function () {
                var result = originalAddClassMethod.apply(this, arguments);

                jQuery(this).trigger('cssClassChanged');

                return result;
            }
        };
    }

}(jQuery));
/**********************************/
/*  NON-JQUERY FUNCTIONS
/**********************************/



/**********************************/
/*  MODULE FUNCTIONS
/**********************************/

/**
 * Generic object functions
 */
var ObjectUtil = (function () {
    "use strict";
    /**
     * Return true or false if the current object is an instance of jQuery
     */
    var isjQuery = function (obj) {
        if (obj instanceof jQuery || 'jquery' in Object(obj)) {
            return true;
        } else {
            return false;
        }
    };

    /**
     * Returns a jquery element from a jquery object array
     * @throws {TypeError} If the supplied parameters are not the correct type
     * @param   {Object} elm   The Jquery element to use
     * @param   {Number} index The index to use for the array
     * @returns {Object} Jquery object from the array
     */
    var getElementFromJqueryArray = function (elm, index) {
        if (!isjQuery(elm)) {
            throw new TypeError("element must be an instance of Jquery");
        }
        if (typeof index !== "number") {
            throw new TypeError("element must be a number");
        }

        return elm.filter(function (i) {
            return i === index;
        });
    };

    var guid = function () {
        function s4() {
            return Math.floor((1 + Math.random()) * 0x10000)
                .toString(16)
                .substring(1);
        }
        return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
            s4() + '-' + s4() + s4() + s4();
    };

    /**
     * Given an array of arguments {Strings} this will return whether all of the strings are not null and have a length of greater than zero
     * after trimming the leading and trailing whitespace.
     * Throws an exception if argument is not of type string
     */
    var validString = function () {
        return _validString.apply(this, arguments);
    };

    var stringContains = function (string, contains) {
        return ~string.indexOf(contains) < 0;
    };

    var deepCompare = function deepCompare() {
        var i, l, leftChain, rightChain;

        function compare2Objects(x, y) {
            var p;

            if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') {
                return true;
            }

            if (x === y) {
                return true;
            }

            if ((typeof x === 'function' && typeof y === 'function') ||
                (x instanceof Date && y instanceof Date) ||
                (x instanceof RegExp && y instanceof RegExp) ||
                (x instanceof String && y instanceof String) ||
                (x instanceof Number && y instanceof Number)) {
                return x.toString() === y.toString();
            }

            if (!(x instanceof Object && y instanceof Object)) {
                return false;
            }

            if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) {
                return false;
            }

            if (x.constructor !== y.constructor) {
                return false;
            }

            if (x.prototype !== y.prototype) {
                return false;
            }

            if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) {
                return false;
            }

            for (p in y) {
                if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
                    return false;
                } else if (typeof y[p] !== typeof x[p]) {
                    return false;
                }
            }

            for (p in x) {
                if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
                    return false;
                } else if (typeof y[p] !== typeof x[p]) {
                    return false;
                }

                switch (typeof (x[p])) {
                    case 'object':
                    case 'function':

                        leftChain.push(x);
                        rightChain.push(y);

                        if (!compare2Objects(x[p], y[p])) {
                            return false;
                        }

                        leftChain.pop();
                        rightChain.pop();
                        break;

                    default:
                        if (x[p] !== y[p]) {
                            return false;
                        }
                        break;
                }
            }

            return true;
        }

        if (arguments.length < 1) {
            return true;
        }

        for (i = 1, l = arguments.length; i < l; i++) {

            leftChain = [];
            rightChain = [];

            if (!compare2Objects(arguments[0], arguments[i])) {
                return false;
            }
        }
        return true;
    };


    /**
     * Extend an object from the base object
     * @throws {Error} If none of the supplied objects are constructors
     * @param   {Function} base Constructor of the base object (to extend from)
     * @param   {Function} sub  Constructor of the sub Object (this object will be the one to be extended)
     * @returns {Function} Chained constructor of the sub object
     */
    var extendObj = function (base, sub) {
        if (typeof sub !== "function") {
            throw new Error("sub must be a Constructor");
        }
        if (typeof base !== "function") {
            throw new Error("base must be a Constructor");
        }
        sub.prototype = Object.create(base.prototype);
        sub.prototype.constructor = sub;
        //  sub.base = base.prototype;
        return sub;
    }

    /**
     * PRIVATE
     * this is called by the public validString because it takes varargs because apply can't be called on the revealing pattern
     */
    var _validString = function () {
        if (arguments == null || arguments.length === 0) {
            return false;
        }

        for (var i = 0; i < arguments.length; i++) {
            var currString = arguments[i];

            if (currString === undefined || currString === null || currString.length === 0) {
                return false
            }


            if (typeof currString !== "string") {
                return false;
            }


            if ($.trim(currString).length === 0) {
                return false;
            }
        }
        return true;
    };

    /**
     * Return an object of public functions
     */
    return {
        isjQuery: isjQuery,
        validString: validString,
        extendObj: extendObj,
        stringContains: stringContains,
        getElementFromJqueryArray: getElementFromJqueryArray,
        deepCompare: deepCompare,
        guid: guid
    };
}());

var DomUtil = (function DomUtil() {
    var injectCss = function (css) {
        if (_isUrl(css)) {
            $("<link>").prop({
                "type": "text/css",
                "rel": "stylesheet"
            }).attr("href", css).appendTo("head");
        } else {
            $("<style>").prop("type", "text/css").html(css).appendTo("head");
        }
    };

    var _isUrl = function (url) {
        var matcher = new RegExp(/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/);
        return matcher.test(url);
    };
    return {
        injectCss: injectCss
    };
}());

/**
 * Generic XML functions
 */
var XmlUtil = (function () {
    "use strict";

    /**
     * Encode XML nodes into HTML entities 
     */
    var encodeXml = function (xml) {
        if (typeof xml !== "string") {
            return;
        }
        return xml.replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&apos;');
    };

    /**
     * Decode encoded HTML Entities.
     * Note: The jQuery method "html" will do this automatically.
     */
    var decodeXml = function (xml) {
        if (typeof xml !== "string") {
            return;
        }
        return xml.replace(/&apos;/g, "'")
            .replace(/&quot;/g, '"')
            .replace(/&gt;/g, '>')
            .replace(/&lt;/g, '<')
            .replace(/&amp;/g, '&');
    };

    return {
        encodeXml: encodeXml,
        decodeXml: decodeXml
    };
}());

var XssEncoder = (function (_super) {
    return _super;
}(XmlUtil || {}));
/**
 * static object containing helpful file functions
 * functions include:
 * 
 * supportsFileApi
 * getFileExt
 * getFileNameFromInput
 * 
 */
var fileUtil = (function () {
    "use strict";

    var getFileNameFromInput = function (input) {
        var currentFile = null;
        // convert jQuery to pure JS
        var input = (function () {
            if (ObjectUtil.isjQuery(input)) {
                if (input.length === 0) {
                    throw "Input does not exist";
                }
                return input[0];
            } else {
                return input;
            }
        }());

        if (input === null) {
            throw "Input does not exist";
        }

        // if the input is a URL
        if (input.getAttribute("type") === "url") {
            return _getFileNameFromUrl(input.value);
        }

        // use the old way of getting a file (not guaranteed to be correct)
        if (!supportsFileApi()) {
            return _getFileNameFromUrl(input.value);
        }

        currentFile = input.files[0];

        return currentFile.name;
    };

    var supportsFileApi = function () {
        if (window.File && window.FileReader && window.FileList && window.Blob) {
            return true;
        } else {
            return false;
        }
    };


    var _getFileNameFromUrl = function (path) {
        if (typeof path !== "string") {
            throw "path does not contain a value, this maybe because you did not pass in an object that represents an input";
        }
        return path.replace(/^C:\\fakepath\\/i, "").split('\\').pop().split('/').pop();
    };

    /**
     * PUBLIC
     * 
     * Will return the file extension from a given filename
     * @param {String} Name of the file
     */
    var getFileExt = function (fullFileName) {
        if (ObjectUtil.isjQuery(fullFileName)) {
            fullFileName = fullFileName.val();
        }
        var ext = fullFileName.substr(fullFileName.lastIndexOf('.') + 1);
        if (ext === "") {
            throw "No file extension";
        } else {
            return ext.toLowerCase();
        }
    };


    var _getHumanReadableSize = function (bytes, decimals) {
        if (bytes == 0) {
            return '0 Byte';
        }
        var k = 1024;
        var dm = decimals + 1 || 3;
        var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
        var i = Math.floor(Math.log(bytes) / Math.log(k));
        return (bytes / Math.pow(k, i)).toPrecision(dm) + ' ' + sizes[i];
    };


    var ImageUtils = (function () {
        /**
         * Get the image info from a file select input
         * @param   {object}   input    The Jquery or pure dom element representing the input
         * @param   {function} callBack The callback function will supply 2 values: the first is an object containing the image infomation and the second is the actual image as a dom element you can use for image preview 
         * @throws {ImageBoundException} If there was an unsupported filetype                             
         */
        var getImageInfoFromInput = function getImageInfoFromInput(input, callBack) {
            if (!supportsFileApi()) {
                return;
            }
            var input = (function () {
                if (ObjectUtil.isjQuery(input)) {
                    if (input.length === 0) {
                        throw "Input does not exist";
                    }
                    return input[0];
                } else {
                    return input;
                }
            }());
            if (BrowserUtils.isChrome()) { // chrome does not support createObjectURL on URL but does on webkitURL... Chrome conplains that you use webkitURL
                window.URL = window.webkitURL;
            }
            var useBlob = true && window.URL;
            var files = input.files;
            if (!files) {
                throw new TypeError("File upload not supported by your browser.");
            }
            if (files.length > 0) {
                var file = files[0];
                if ((/\.(png|jpg|gif)$/i).test(file.name)) {
                    var deferred = $.Deferred();
                    $.when(readImage(file, deferred)).done(function (value, img) {
                        callBack.call(img, value, img);
                    });
                } else {
                    throw new ImageBoundException("Unsupported Image extension: '" + getFileExt(file.name) + "' file name: '" + file.name + "'");
                }
            }


            function readImage(file, defer) {
                var reader = new FileReader();
                reader.addEventListener("load", function () {
                    var image = new Image();
                    image.addEventListener("load", function () {
                        var returnObj = {};
                        Object.defineProperties(returnObj, {
                            "fileName": {
                                value: file.name
                            },
                            "imageWidth": {
                                value: image.width
                            },
                            "imageHeight": {
                                value: image.height
                            },
                            "imageType": {
                                value: file.type
                            },
                            "fileSize": {
                                value: _getHumanReadableSize(file.size)
                            },
                            "base64": {
                                value: reader.result
                            }
                        })
                        Object.freeze(returnObj); // stop anything from changing anything in this object
                        defer.resolve(returnObj, this);
                    });
                    image.src = useBlob ? window.URL.createObjectURL(file) : reader.result;
                    if (useBlob) {
                        window.URL.revokeObjectURL(file);
                    }
                });
                reader.readAsDataURL(file);
                return defer.promise();
            }
        }
        return {
            getImageInfoFromInput: getImageInfoFromInput
        };
    }());

    /**
     * Get the image info from a file select input
     * @param   {String}   b64Data    The base 64 data
     * @param   {String} contentType  The content type of this blob example : (image/png)
     * @param   {String} sliceSize    The slices of the array to use, this is optinal and if omited will default to 512bits per array. this acts as a buffer
     */
    var b64toBlob = function b64toBlob(b64Data, contentType, sliceSize) {
        contentType = contentType || '';
        sliceSize = sliceSize || 512;

        var byteCharacters = atob(b64Data);
        var byteArrays = [];

        for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
            var slice = byteCharacters.slice(offset, offset + sliceSize);

            var byteNumbers = new Array(slice.length);
            for (var i = 0; i < slice.length; i++) {
                byteNumbers[i] = slice.charCodeAt(i);
            }

            var byteArray = new Uint8Array(byteNumbers);

            byteArrays.push(byteArray);
        }

        var blob = new Blob(byteArrays, {
            type: contentType
        });
        return blob;
    }

    /**
     * Return an object of public functions
     */
    return {
        getFileExt: getFileExt,
        getFileNameFromInput: getFileNameFromInput,
        supportsFileApi: supportsFileApi,
        ImageUtils: ImageUtils,
        b64toBlob: b64toBlob
    };
}());


/**
 * Drag and drop object
 * Extends the fileUtil object
 */
var dragDrop = (function (_super, window) {
    var canSupportDragAndDrop = function () {
        return _super.supportsFileApi();
    };
    return {
        fileUtil: _super,
        canSupportDragAndDrop: canSupportDragAndDrop
    };
}(fileUtil || {}, window));


var ArrayUtils = (function () {
    "use strict";

    /**
     * DEPRICATED!! Used yourArray._remove_(itemToRemove);
     */
    var removeFromArray = function (arr, obj) {
        if (console) {
            console.warn("The use of 'removeFromArray' is depricated, please use 'yourArray._remove_(item)' instead");
        }
        var i = arr.length;
        while (i--) {
            if (ObjectUtil.isjQuery(obj)) {
                if ($(arr[i]).is(obj)) {
                    arr.splice(i, 1);
                }
            } else {
                if (arr[i] === obj) {
                    arr.splice(i, 1);
                }
            }
        }
    };

    var arrayCopy = function (array, deep) {
        if (!Array.isArray(array)) {
            throw new TypeError("array to copy must be of type 'Array'");
        }
        deep = typeof deep === "undefined" ? false : deep;
        return $.extend(deep, [], array);
    };

    return {
        removeFromArray: removeFromArray,
        arrayCopy: arrayCopy
    };
}());

var ColourUtils = (function () {
    "use strict";
    var h = Math.random();
    var golden_ratio_conjugate = 0.618033988749895;

    //http://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/
    var getRandomColour = function () {
        //Used to generate random colors
        h += golden_ratio_conjugate
        h %= 1

        function hsv_to_rgb(h, s, v) {
            var h_i = parseInt(h * 6);
            var f = h * 6 - h_i;
            var p = v * (1 - s);
            var q = v * (1 - f * s);
            var t = v * (1 - (1 - f) * s);
            var r, g, b;
            if (h_i == 0) {
                r = v;
                g = t;
                b = p;
            } else if (h_i == 1) {
                r = q;
                g = v;
                b = p;
            } else if (h_i == 2) {
                r = p;
                g = v;
                b = t;
            } else if (h_i == 3) {
                r = p;
                g = q;
                b = v;
            } else if (h_i == 4) {
                r = t;
                g = p;
                b = v;
            } else if (h_i == 5) {
                r = v;
                g = p;
                b = q;
            }
            return "rgb(" + parseInt(r * 256) + "," + parseInt(g * 256) + "," + parseInt(b * 256) + ")";
        }
        return hsv_to_rgb(h, 0.7, 0.75);
    };

    return {
        getRandomColour: getRandomColour
    };
}());

var BrowserUtils = (function () {
    "use strict";
    var detectMobile = function () {
        var check = false;
        (function (a) {
            if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) {
                check = true;
            }
        })(navigator.userAgent || navigator.vendor || window.opera);
        return check;
    };

    var isChrome = function () {
        if (window.chrome !== null && window.navigator.vendor === 'Google Inc.') {
            return true;
        } else {
            return false;
        }
    };

    var isFireFox = function () {
        if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
            return true;
        } else {
            return false;
        }
    };

    var isWebkit = function () {
        return /webkit/.test(navigator.userAgent.toLowerCase());
    };

    var isEdgeOrIe = function isEdgeOrIe() {
        return Edge.isEdge() || IeUtils.isIe();
    };

    var Edge = (function () {
        var getVersion = function getVersion() {
            var ua = window.navigator.userAgent;
            var edge = ua.indexOf('Edge/');
            if (edge > 0) {
                return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
            } else {
                return null;
            }
        };

        var isEdge = function isEdge() {
            return getVersion() !== null;
        };
        return {
            getVersion: getVersion,
            isEdge: isEdge
        }
    }());

    return {
        detectMobile: detectMobile,
        isChrome: isChrome,
        isFireFox: isFireFox,
        isWebkit: isWebkit,
        isEdgeOrIe: isEdgeOrIe,
        Edge: Edge
    };
}());


/**
 * An object for detecting ie versions
 * Functions available are:
 * 
 * isIe
 * getIeVersion
 */
var IeUtils = (function () {

    "use strict";

    /**
     * returns true or false if using IE10
     */
    var _isIE10 = function () {
        return navigator.appVersion.indexOf("MSIE 10") !== -1;
    };

    /**
     * returns true or false if using IE11
     */
    var _isIE11 = function () {
        return !!navigator.userAgent.match(/Trident.*rv[ :]*11\./);
    };

    var _isIE9 = function () {
        return navigator.appVersion.indexOf("MSIE 9.") != -1;
    };

    /**
     * Returns if using ie
     * returns boolean
     */
    var isIe = function () {
        var ua = window.navigator.userAgent;
        var msie = ua.indexOf("MSIE ");
        if (msie > 0 | _isIE11() || _isIE10()) {
            return true;
        } else {
            return false;
        }
    };

    /**
     * returns the version of IE used by the client
     * returns string or null if not using IE
     */
    var getIeVersion = function () {
        if (!isIe()) {
            return null;
        }
        var actualVersion = "unknown";
        var jscriptMap = {
            "5.5": 5.5,
            "5.6": 6,
            "5.7": 7,
            "5.8": 8,
            "9": 9,
        };
        var jscriptVersion = new Function("/*@cc_on return @_jscript_version; @*/")();
        if (typeof jscriptVersion !== "undefined") {
            actualVersion = jscriptMap[jscriptVersion];
            // Somehow, the script version was retrived but not known (emulation mode can cause this issue)
            if (typeof actualVersion === "undefined") {
                if (_isIE10()) {
                    actualVersion = 10;
                } else if (_isIE11()) {
                    actualVersion = 11;
                } else if (_isIE9()) {
                    actualVersion = 9;
                } else {
                    actualVersion = "unknown";
                }
            }
        } else {
            if (_isIE10()) {
                actualVersion = 10;
            } else if (_isIE11()) {
                actualVersion = 11;
            }
        }
        return actualVersion;
    };

    /**
     * Returns an object of public functions 
     */
    return {
        isIe: isIe,
        getIeVersion: getIeVersion
    };
}());


var VersionUtil = (function () {
    var isHigherVersion = function (version1, version2) {
        if (typeof version1 !== "string" && typeof version2 !== "string") {
            throw "versions must be strings";
        }
        var v1Comps = version1.split(".");
        var v2Comps = version2.split(".");
        var limit = (v1Comps.length > v2Comps.length) ? v2Comps.length : v1Comps.length;
        for (var i = 0; i < limit; i++) {
            var v1part = parseInt(v1Comps[i]);
            var v2part = parseInt(v2Comps[i]);
            if (v1part > v2part) {
                return true;
            }
            if (v2part > v1part) {
                return false;
            }
        }
        return v1Comps.length >= v2Comps.length;
    };

    return {
        isHigherVersion: isHigherVersion
    };
}());

var QueryString = (function () {
    var query_string = {};
    var query = window.location.search.substring(1);
    var vars = query.split("&");
    for (var i = 0; i < vars.length; i++) {
        var pair = vars[i].split("=");
        if (typeof query_string[pair[0]] === "undefined") {
            query_string[pair[0]] = pair[1];
        } else if (typeof query_string[pair[0]] === "string") {
            var arr = [query_string[pair[0]], pair[1]];
            query_string[pair[0]] = arr;
        } else {
            query_string[pair[0]].push(pair[1]);
        }
    }
    return query_string;
}());


/**********************************/
/*  PROTOTYPE FUNCTIONS
/**********************************/

/**
 * Adding sort array of objects to the prototype.
 * To use this. call sortBy on an array of objects. for example given this object:
 * var arr= [
 *	 	{
 *	 		AgencyId:"SDMX",
 *	 		Id:"AGENCIES",
 *	 		Name:"SDMX Agency Scheme"
 *	 	},
 *	 	{
 *	 		AgencyId:"METATECH",
 *	 		Id:"AGENCIES",
 *	 		Name:"Agencies"
 *	 	},
 *	 ]
 * this is an array of 2 objects.
 * if we want to sort this by the property "Name" we can do this
 * arr.sortBy("Name");
 * if you want to sort by "name" and "Id", you can just do
 * arr.sortBy("Name", "Id");
 * this takes an unlimited amount of args.
 * 
 * If you wish to sort Descending, append a "-" to your argument
 * 
 * sort by "Name" Descending: arr.sortBy("-Name");
 */
(function () {
    function _sortByAttr(attr) {
        var sortOrder = 1;
        if (attr[0] == "-") {
            sortOrder = -1;
            attr = attr.substr(1);
        }
        return function (a, b) {
            if (typeof a[attr] === "undefined" || typeof b[attr] === "undefined") {
                throw "There is no property with the value of " + attr.toString() + " inside this object";
            }
            var result = a[attr].toUpperCase().localeCompare(b[attr].toUpperCase());
            return result * sortOrder;
        }
    }

    function _getSortFunc() {
        if (arguments.length == 0) {
            throw "Zero length arguments not allowed for Array.sortBy()";
        }
        var args = arguments;
        return function (a, b) {
            for (var result = 0, i = 0; result == 0 && i < args.length; i++) {
                result = _sortByAttr(args[i])(a, b);
            }
            return result;
        }
    }
    Object.defineProperty(Array.prototype, "sortBy", {
        enumerable: false,
        writable: true,
        value: function () {
            return this.sort(_getSortFunc.apply(null, arguments));
        }
    });
}());
(function () {
    if (Array.prototype._remove_) {
        return;
    }
    Object.defineProperty(Array.prototype, "_remove_", {
        enumerable: false,
        /**
         * Removes all occurence of specified item from array
         * @param this Array
         * @param itemToRemove Item to remove from array
         */
        value: function (itemToRemove) {
            var i = this.length;
            try {
                var itemToRemoveStr = null
                itemToRemoveStr = JSON.stringify(itemToRemove);
            } catch (e) {}
            while (i--) {
                var currentItem = this[i];
                var currentItemStr = null;
                if (itemToRemoveStr !== null) {
                    try {
                        currentItemStr = JSON.stringify(currentItem);
                    } catch (e) {}
                }
                if (ObjectUtil.isjQuery(itemToRemove)) {
                    if ($(currentItem).is(itemToRemove)) {
                        this.splice(i, 1);
                    }
                } else {
                    if (currentItemStr !== null && itemToRemoveStr !== null) {
                        if (currentItemStr === itemToRemoveStr) {
                            this.splice(i, 1);
                            continue;
                        }
                    }
                    if (ObjectUtil.deepCompare(itemToRemove, currentItem)) {
                        this.splice(i, 1);
                        continue;
                    }
                    continue;
                }
            }
        }
    });

    if (Array.prototype.moveItem) {
        return;
    }
    Object.defineProperty(Array.prototype, "moveItem", {
        value: function (old_index, new_index) {
            while (old_index < 0) {
                old_index += this.length;
            }
            while (new_index < 0) {
                new_index += this.length;
            }
            if (new_index >= this.length) {
                var k = new_index - this.length;
                while ((k--) + 1) {
                    this.push(undefined);
                }
            }
            this.splice(new_index, 0, this.splice(old_index, 1)[0]);
        }
    })
}());
(function () {
    Object.defineProperty(Function.prototype, "getName", {
        value: function () {
            try {
                return /^function\s+([\w\$]+)\s*\(/.exec(this.toString())[1];
            } catch (e) {
                return null;
            }
        }
    })
}());
var LinkedArray = (function () {
    var LinkedArray = function LinkedArray(type) {
        if (typeof type === "undefined") {
            throw new TypeError("a type must be supplied");
        }
        var arr = [];
        Object.setPrototypeOf(arr, LinkedArray.prototype); // this is bad. however, this is one of them "if it's last resort" things. becase this truly is last resort, we must edit the __proto__ directly to extend array, object.create will not work
        Object.defineProperty(arr, "_type", {
            value: type
        });
        return arr;
    };

    LinkedArray.prototype = new Array;

    LinkedArray.prototype.push = function push() {
        var argarr = arguments;
        var args = (arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments));
        for (var i = 0; i < args.length; i++) {
            var carrArg = args[i];
            if (Array.isArray(this._type)) {
                if (!Array.isArray(carrArg)) {
                    throw new TypeError("this array only accepts instance of arrays");
                }
            } else if (typeof this._type === "object" || typeof this._type === "function") {
                if (typeof carrArg === "undefined") {
                    break;
                }
                if (!(carrArg instanceof this._type)) {
                    var msg = "This array only accepts instance of {supplied object}";
                    if (this._type.name !== "") {
                        msg = "This array only accepts instance of " + this._type.name;
                    } else {
                        try {
                            msg = "This array only accepts instance of " + this._type.getName();
                        } catch (e) {}
                    }
                    throw new TypeError(msg);
                }
            } else if (typeof this._type === "number" && typeof carrArg !== "number") {
                throw new TypeError("this array only accepts " + typeof this._type);
            } else if (typeof this._type === "boolean" && typeof carrArg !== "boolean") {
                throw new TypeError("this array only accepts " + typeof this._type);
            } else if (typeof this._type === "string" && typeof carrArg !== "string") {
                throw new TypeError("this array only accepts " + typeof this._type);
            }
        }
        args.forEach(function (element, index, array) {
            Array.prototype.push.call(this, element);
        }, this);
    };

    LinkedArray.prototype.getType = function getType() {
        if (typeof this._type === "function") {
            return new(this._type);
        } else if (typeof this._type === "object") {
            return this._type
        } else {
            return typeof this._type;
        }

    };

    return LinkedArray;
}());
/**********************************/
/*  Custom Exceptions
/**********************************/
var ImageBoundException = (function () {
    function ImageBoundException(message) {
        this.name = 'ImageBoundException';
        this.message = message || 'An error occured with this image';
        this.stack = (new Error()).stack;
    }
    ImageBoundException.prototype = Object.create(Error.prototype);
    ImageBoundException.prototype.constructor = ImageBoundException;
    return ImageBoundException;
}());
/**********************************/
/*  PROTOTYPE FUNCTIONS END
/**********************************/