JsUtils

JavaScript Utility functions

Od 25.04.2016.. Pogledajte najnovija verzija.

Ovu skriptu ne treba izravno instalirati. To je biblioteka za druge skripte koje se uključuju u meta direktivu // @require https://update.greatest.deepsurf.us/scripts/19117/121891/JsUtils.js

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

/*
 ____________________
< 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
/**********************************/