(function(){var N=this;N.N2_=N.N2_||{r:[],d:[]},N.N2R=N.N2R||function(){N.N2_.r.push(arguments)},N.N2D=N.N2D||function(){N.N2_.d.push(arguments)}}).call(window);
N2R('$', function ($) {

    $.extend(window.nextend, {
        fontManager: null,
        styleManager: null,
        animationManager: null,
        browse: null,
        askToSave: true,
        cancel: function (url) {
            nextend.askToSave = false;
            window.location.href = url;
            return false;
        }
    });

    window.n2_ = function (text) {
        if (typeof nextend.localization[text] !== 'undefined') {
            return nextend.localization[text];
        }
        return text;
    };

    window.n2_printf = function (text) {
        var args = arguments;
        var index = 1;
        return text.replace(/%s/g, function () {
            return args[index++];
        });
    };

    /**
     * Helps us to track when the user loaded the page
     */
    window.nextendtime = $.now();

    window.nextend.roundTo = 5;
    window.nextend.roundHelper = function (value) {
        if (window.nextend.roundTo <= 1) {
            return value;
        }

        return Math.round(value / window.nextend.roundTo) * window.nextend.roundTo;
    };

    $.fn.n2opener = function () {
        return this.each(function () {
            var opener = $(this).on("click", function (e) {
                opener.toggleClass("n2-active");
            });

            opener.siblings('span').on("click", function (e) {
                opener.toggleClass("n2-active");
            });

            opener.parent().on("mouseleave", function () {
                opener.removeClass("n2-active");
            });
            opener.find(".n2-button-menu").on("click", function (e) {
                e.stopPropagation();
                opener.removeClass("n2-active");
            });
        });
    };

    if (typeof jQuery !== 'undefined') {
        jQuery(document).on('wp-collapse-menu', function () {
            $(window).trigger('resize');
        });
    }


    nextend.deepDiff = function () {
        return {
            map: function (obj1, obj2) {
                if (this.isValue(obj1)) {
                    if ('undefined' != typeof(obj1) && obj1 != obj2) {
                        return obj1;
                    } else {
                        return undefined;
                    }
                }

                for (var key in obj2) {
                    if (this.isFunction(obj2[key])) {
                        continue;
                    }

                    obj1[key] = this.map(obj1[key], obj2[key]);
                    if (obj1[key] === undefined || ($.isPlainObject(obj1[key]) && $.isEmptyObject(obj1[key])) || (this.isArray(obj1[key]) && obj1[key].length == 0)) {
                        delete obj1[key];
                    }
                }


                return obj1;

            },

            isFunction: function (obj) {
                return {}.toString.apply(obj) === '[object Function]';
            },
            isArray: function (obj) {
                return {}.toString.apply(obj) === '[object Array]';
            },
            isObject: function (obj) {
                return {}.toString.apply(obj) === '[object Object]';
            },
            isValue: function (obj) {
                return !this.isObject(obj) && !this.isArray(obj);
            }
        }
    }();


    nextend.UnicodeToHTMLEntity = function (s) {
        try {
            var patt = /(?:[\uD800-\uDBFF][\uDC00-\uDFFF])/g,
                match;

            function surrogatePairToCodePoint(charCode1, charCode2) {
                return ((charCode1 & 0x3FF) << 10) + (charCode2 & 0x3FF) + 0x10000;
            }

            function stringToCodePointArray(str) {
                var codePoints = [],
                    i = 0,
                    charCode;
                while (i < str.length) {
                    charCode = str.charCodeAt(i);
                    if ((charCode & 0xF800) == 0xD800) {
                        codePoints.push(surrogatePairToCodePoint(charCode, str.charCodeAt(++i)));
                    } else {
                        codePoints.push(charCode);
                    }
                    ++i;
                }
                return '&#' + codePoints + ';';
            }

            while ((match = patt.exec(s))) {
                s = s.substr(0, match.index) + stringToCodePointArray(s.substr(match.index, patt.lastIndex - match.index)) + s.substr(patt.lastIndex);
            }
        } catch (e) {
            console.error(e);
            return s;
        }

        return s;
    };
});
N2D('NextendHeadingPane', function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param $node
     * @param headings
     * @param contents
     * @param identifier
     * @constructor
     */
    function NextendHeadingPane($node, headings, contents, identifier) {
        this.$node = $node.data('pane', this);
        this.headings = headings;
        this.contents = contents;
        this.tabNames = [];
        this.headings.each($.proxy(function (i, el) {
            this.tabNames.push($(el).data('tab'));
        }, this));
        this.identifier = identifier;

        this._active = headings.index(headings.filter('.n2-active'));

        for (var i = 0; i < headings.length; i++) {
            headings.eq(i).on('click', $.proxy(this.switchToPane, this, i));
        }

        if (identifier) {
            var saved = $.jStorage.get(this.identifier + "-pane", -1);
            if (saved != -1) {
                this.switchToPane(saved);
                return;
            }
        }
        this.hideAndShow();
    }


    NextendHeadingPane.prototype.switchToPane = function (i, e) {
        if (e) {
            e.preventDefault();
        }
        this.headings.eq(this._active).removeClass('n2-active');
        this.headings.eq(i).addClass('n2-active');
        this._active = i;
        this.hideAndShow();
        this.store(this._active);

        this.$node.triggerHandler('changetab');
    };

    NextendHeadingPane.prototype.hideAndShow = function () {
        $(this.contents[this._active]).css('display', 'block').trigger('activate');
        for (var i = 0; i < this.contents.length; i++) {
            if (i != this._active) {
                $(this.contents[i]).css('display', 'none');
            }
        }
    };

    NextendHeadingPane.prototype.store = function (i) {
        if (this.identifier) {
            $.jStorage.set(this.identifier + "-pane", i);
        }
    };

    NextendHeadingPane.prototype.showTabs = function (tabNames) {
        var activatedFirst = false;
        for (var i = 0; i < this.tabNames.length; i++) {
            if ($.inArray(this.tabNames[i], tabNames) != '-1') {
                this.headings.eq(i).css('display', '');
                $(this.contents[i]).css('display', '');
                if (i == this._active) {
                    activatedFirst = i;
                } else if (activatedFirst === false) {
                    activatedFirst = i;
                }
            } else {
                this.headings.eq(i).css('display', 'none');
                $(this.contents[i]).css('display', 'none');
            }
        }
        this.switchToPane(activatedFirst);
    };

    return NextendHeadingPane;
});
N2D('NextendHeadingScrollToPane', function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param headings
     * @param contents
     * @param identifier
     * @constructor
     */
    function NextendHeadingScrollToPane(headings, contents, identifier) {
        this.headings = headings;
        this.contents = contents;
        this.identifier = identifier;

        for (var i = 0; i < headings.length; i++) {
            headings.eq(i).on('click', $.proxy(this.scrollToPane, this, i));
        }
    }

    NextendHeadingScrollToPane.prototype.scrollToPane = function (i, e) {
        if (e) {
            e.preventDefault();
        }
        $('html, body').animate({
            scrollTop: this.contents[i].offset().top - $('.n2-main-top-bar').height() - $('#wpadminbar, .navbar-fixed-top').height() - 10
        }, 1000);
    };

    return NextendHeadingScrollToPane;
});
N2D('WindowManager', function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @constructor
     */
    function WindowManager() {
        this.window = ['main'];
        this.mouseDownArea = false;
        this.timeout = null;

        this.isPreventDblClick = false;
        this.dblClickTimeout = null;
    }

    WindowManager.prototype.addWindow = function (name) {
        this.window.push(name);
    };

    WindowManager.prototype.removeWindow = function () {
        this.window.pop();
    };

    WindowManager.prototype.getCurrentWindow = function () {
        return this.window[this.window.length - 1];
    };

    WindowManager.prototype.setMouseDownArea = function (area, e) {
        this.mouseDownArea = area;
        if (this.timeout) {
            clearTimeout(this.timeout);
        }
        this.timeout = setTimeout($.proxy(function () {
            this.timeout = null;
            this.mouseDownArea = false;
        }, this), 50);
    };

    WindowManager.prototype.preventDblClick = function () {
        this.isPreventDblClick = true;
        if (this.dblClickTimeout) {
            clearTimeout(this.dblClickTimeout);
        }
        this.dblClickTimeout = setTimeout($.proxy(function () {
            this.dblClickTimeout = null;
            this.isPreventDblClick = false;
        }, this), 200);
    };

    var windowManagerInstance = new WindowManager();
    /**
     * @returns {N2Classes.WindowManager}
     */
    WindowManager.get = function () {
        return windowManagerInstance;
    };

    WindowManager.setMouseDownArea = function () {
        windowManagerInstance.setMouseDownArea.apply(windowManagerInstance, arguments);
    };

    return WindowManager;
});
N2D('AjaxHelper', function ($, undefined) {

    var loader = null;

    /**
     * @memberOf N2Classes
     * @constructor
     */
    function AjaxHelper() {
    }

    AjaxHelper.query = {};

    AjaxHelper.addAjaxLoader = function () {
        loader = $('<div class="n2-loader-overlay"><div class="n2-loader"></div></div>')
            .appendTo('body');
    };

    AjaxHelper.addAjaxArray = function (parts) {
        for (var k in parts) {
            AjaxHelper.query[k] = parts[k];
        }
    };

    AjaxHelper.makeAjaxQuery = function (queryArray, isAjax) {
        if (isAjax) {
            queryArray['mode'] = 'ajax';
            queryArray['nextendajax'] = '1';
        }
        for (var k in AjaxHelper.query) {
            queryArray[k] = AjaxHelper.query[k];
        }
        return N2Classes.N2QueryString.stringify(queryArray);
    };

    AjaxHelper.makeAjaxUrl = function (url, queries) {
        var urlParts = url.split("?");
        if (urlParts.length < 2) {
            urlParts[1] = '';
        }
        var parsed = N2Classes.N2QueryString.parse(urlParts[1]);
        if (typeof queries != 'undefined') {
            for (var k in queries) {
                parsed[k] = queries[k];
            }
        }
        return urlParts[0] + '?' + AjaxHelper.makeAjaxQuery(parsed, true);
    };

    AjaxHelper.makeFallbackUrl = function (url, queries) {
        var urlParts = url.split("?");
        if (urlParts.length < 2) {
            urlParts[1] = '';
        }
        var parsed = N2Classes.N2QueryString.parse(urlParts[1]);
        if (typeof queries != 'undefined') {
            for (var k in queries) {
                parsed[k] = queries[k];
            }
        }
        return urlParts[0] + '?' + AjaxHelper.makeAjaxQuery(parsed, false);
    };

    AjaxHelper.ajax = function (ajax) {
        AjaxHelper.startLoading();
        return $.ajax(ajax).always(function (response, status) {
            AjaxHelper.stopLoading();
            try {

                if (status != 'success') {
                    response = JSON.parse(response.responseText);
                } else if (typeof response === 'string') {
                    response = JSON.parse(response);
                }
                if (response.redirect !== undefined) {
                    AjaxHelper.startLoading();
                    window.location.href = response.redirect;
                    return;
                }

                AjaxHelper.notification(response);
            } catch (e) {
                var pattern = /<body[^>]*>((.|[\n\r])*)<\/body>/im,
                    matches = pattern.exec(response.responseText);
                if (matches.length) {
                    N2Classes.NextendModal.SafeHTML(response.status, matches[1]);
                } else {
                    console.error(response.responseText, response);
                }
            }
        });
    };

    AjaxHelper.notification = function (response) {

        if (typeof response.notification !== 'undefined' && response.notification) {
            for (var k in response.notification) {
                for (var i = 0; i < response.notification[k].length; i++) {
                    N2Classes.Notification[k](response.notification[k][i][0], response.notification[k][i][1]);
                }
            }
        }
    };

    AjaxHelper.getJSON = function (ajax) {
        AjaxHelper.startLoading();
        return $.getJSON(ajax).always(function () {
            AjaxHelper.stopLoading();
        });
    };

    AjaxHelper.startLoading = function () {
        loader.addClass('n2-active');
    };

    AjaxHelper.stopLoading = function () {
        loader.removeClass('n2-active');
    };

    N2R('documentReady', function () {
        AjaxHelper.addAjaxLoader();
    });

    return AjaxHelper;
});
N2D('Esc', function ($, undefined) {

    /**
     * @alias N2Classes.Esc
     * @constructor
     */
    function Esc() {
        this.FiLo = [];
        this.doc = $(document);
        this.isListening = false;
    }

    Esc.prototype.add = function (callback) {
        this.FiLo.push(callback);

        if (!this.isListening) {
            this.doc.on('keydown.n2-esc', $.proxy(function (e) {
                if ((e.keyCode == 27 || e.keyCode == 8)) {
                    if (!$(e.target).is("input, textarea")) {
                        e.preventDefault();
                        var ret = this.FiLo[this.FiLo.length - 1]();
                        if (ret) {
                            this.pop();
                        }
                    } else if (e.keyCode == 27) {
                        e.preventDefault();
                        $(e.target).blur();
                    }
                }
            }, this));
            this.isListening = true;
        }
    };

    Esc.prototype.pop = function () {
        this.FiLo.pop();
        if (this.FiLo.length === 0) {
            this.doc.off('keydown.n2-esc');
            this.isListening = false;
        }
    };

    return new Esc();
});
N2D('tooltip', function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @constructor
     */
    function Tooltip() {
        this.$element = $('<div class="n2 n2-tooltip n2-radius-m"></div>');
        this.timeout = null;
        this.$tipFor = null;


        $(window).ready($.proxy(this.ready, this));

    }

    Tooltip.prototype.ready = function () {
        this.$element.appendTo('body');
        this.add($('body'));
    };


    Tooltip.prototype.add = function ($parent) {
        $parent.find('[data-n2tip]').off('.n2hastip').on({
            'mouseenter.n2hastip': $.proxy(this.onEnter, this)
        });
    };


    Tooltip.prototype.addElement = function ($el, title, h, v) {
        $el.data({
            n2tip: title,
            n2tipv: v,
            n2tiph: h
        }).off('.n2hastip').on({
            'mouseenter.n2hastip': $.proxy(this.onEnter, this)
        });
    };


    Tooltip.prototype.onEnter = function (e) {
        if (this.timeout) {
            clearTimeout(this.timeout);
        }
        this.$tipFor = $(e.currentTarget).on({
            'mousemove.n2tip': $.proxy(this.onMove, this),
            'mouseleave.n2tip': $.proxy(this.onLeave, this)
        });
        this.onMove(e);
        this.timeout = setTimeout($.proxy(function () {
            var v = this.$tipFor.data('n2tipv'),
                h = this.$tipFor.data('n2tiph');
            if (typeof v === 'undefined') {
                v = 10;
            }
            if (typeof h === 'undefined') {
                h = 10;
            }
            this.$element.css({
                margin: v + 'px ' + h + 'px'
            }).html(this.$tipFor.data('n2tip')).addClass('n2-active');
        }, this), 500);
    };


    Tooltip.prototype.onMove = function (e) {
        this.$element.css({
            left: e.pageX,
            top: e.pageY
        });
    };


    Tooltip.prototype.onLeave = function (e) {
        if (this.timeout) {
            clearTimeout(this.timeout);
        }
        if (this.$tipFor) {
            this.$tipFor.off('.n2tip');
            this.$tipFor = null;
            this.$element.removeClass('n2-active').css('margin', '');
        }
    };


    function TooltipMouse() {
        this.isVisible = false;
        this.$body = $('body');
        this.$element = $('<div class="n2 n2-tooltip n2-radius-m"></div>').appendTo(this.$body);
    }

    TooltipMouse.prototype.show = function (text, e) {
        if (this.isVisible) {
            this.$element.html(text);
        } else {
            this.isVisible = true;
            this.$body.on('mousemove.tooltipMouse', $.proxy(this.mouseMove, this));
            this.mouseMove(e);
            this.$element.html(text).addClass('n2-active');
        }
    };


    TooltipMouse.prototype.mouseMove = function (e) {
        this.$element.css({
            left: e.pageX + 10,
            top: e.pageY + 10
        });
    };


    TooltipMouse.prototype.hide = function () {
        this.$body.off('mousemove.tooltipMouse');
        this.$element.removeClass('n2-active').html('');
        this.isVisible = false;
    };


    nextend.tooltip = new Tooltip();

    $(window).ready(function () {
        nextend.tooltipMouse = new TooltipMouse();
    });

    return nextend.tooltip;
});
/**
 * Convert 8 char hexadecimal color into RGBA color
 * @param 8 characters of hexadecimal color value. Last two character stands for alpha 0-255
 * @returns RGBA representation string
 */

window.N2Color = {
    hex2rgba: function (str) {
        var num = parseInt(str, 16); // Convert to a number
        return [num >> 24 & 255, num >> 16 & 255, num >> 8 & 255, (num & 255) / 255];
    },
    hex2rgbaCSS: function (str) {
        return 'RGBA(' + N2Color.hex2rgba(str).join(',') + ')';
    },
    hexdec: function (hex_string) {
        hex_string = (hex_string + '').replace(/[^a-f0-9]/gi, '');
        return parseInt(hex_string, 16);
    },

    hex2alpha: function (str) {
        var num = parseInt(str, 16); // Convert to a number
        return ((num & 255) / 255).toFixed(3);
    },
    colorizeSVG: function (str, color) {
        var parts = str.split('base64,');
        if (parts.length == 1) {
            return str;
        }
        parts[1] = N2Classes.Base64.encode(N2Classes.Base64.decode(parts[1]).replace('fill="#FFF"', 'fill="#' + color.substr(0, 6) + '"').replace('opacity="1"', 'opacity="' + N2Color.hex2alpha(color) + '"'));
        return parts.join('base64,');
    },
    colorToSVG: function (str) {
        var num = parseInt(str, 16); // Convert to a number

        return [str.substr(0, 6), (num & 255) / 255];
    }
};
/*!
 query-string
 Parse and stringify URL query strings
 https://github.com/sindresorhus/query-string
 by Sindre Sorhus
 MIT License
 */
N2D('N2QueryString', function ($, undefined) {
    'use strict';

    /**
     * @memberOf N2Classes
     *
     * @type {{parse: parse, stringify: function(*=): string}}
     */
    var N2QueryString = {
        parse: function (str) {
            if (typeof str !== 'string') {
                return {};
            }

            str = str.trim().replace(/^(\?|#)/, '');

            if (!str) {
                return {};
            }

            return str.trim().split('&').reduce(function (ret, param) {
                var parts = param.replace(/\+/g, ' ').split('=');
                var key = parts[0];
                var val = parts[1];

                key = decodeURIComponent(key);
                // missing `=` should be `null`:
                // http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters
                val = val === undefined ? null : decodeURIComponent(val);

                if (!ret.hasOwnProperty(key)) {
                    ret[key] = val;
                } else if (Array.isArray(ret[key])) {
                    ret[key].push(val);
                } else {
                    ret[key] = [ret[key], val];
                }

                return ret;
            }, {});
        },
        stringify: function (obj) {
            return obj ? Object.keys(obj).map(function (key) {
                var val = obj[key];

                if (Array.isArray(val)) {
                    return val.map(function (val2) {
                        return encodeURIComponent(key) + '=' + encodeURIComponent(val2);
                    }).join('&');
                }

                return encodeURIComponent(key) + '=' + encodeURIComponent(val);
            }).join('&') : '';

        }
    };

    return N2QueryString;
});

!function (g) {
    var $0 = [], // result
        $1 = [], // tail
        $2 = [], // blocks
        $3 = [], // s1
        $4 = ("0123456789abcdef").split(""), // hex
        $5 = [], // s2
        $6 = [], // state
        $7 = false, // is state created
        $8 = 0, // len_cache
        $9 = 0, // len
        BUF = [];

    // use Int32Array if defined
    if (g.Int32Array) {
        $1 = new Int32Array(16);
        $2 = new Int32Array(16);
        $3 = new Int32Array(4);
        $5 = new Int32Array(4);
        $6 = new Int32Array(4);
        BUF = new Int32Array(4);
    } else {
        var i;
        for (i = 0; i < 16; i++) $1[i] = $2[i] = 0;
        for (i = 0; i < 4; i++) $3[i] = $5[i] = $6[i] = BUF[i] = 0;
    }

    // fill s1
    $3[0] = 128;
    $3[1] = 32768;
    $3[2] = 8388608;
    $3[3] = -2147483648;

    // fill s2
    $5[0] = 0;
    $5[1] = 8;
    $5[2] = 16;
    $5[3] = 24;

    function encode(s) {
        var utf = enc = "",
            start = end = 0;

        for (var i = 0, j = s.length; i < j; i++) {
            var c = s.charCodeAt(i);

            if (c < 128) {
                end++;
                continue;
            } else if (c > 127 && c < 2048)
                enc = String.fromCharCode((c >> 6) | 192, (c & 63) | 128);
            else
                enc = String.fromCharCode((c >> 12) | 224, ((c >> 6) & 63) | 128, (c & 63) | 128);

            if (end > start)
                utf += s.slice(start, end);

            utf += enc;
            start = end = i + 1;
        }

        if (end > start)
            utf += s.slice(start, j);

        return utf;
    }

    function md5_update(s) {
        var i, I;

        s += "";
        $7 = false;
        $8 = $9 = s.length;

        if ($9 > 63) {
            getBlocks(s.substring(0, 64));
            md5cycle($2);
            $7 = true;

            for (i = 128; i <= $9; i += 64) {
                getBlocks(s.substring(i - 64, i));
                md5cycleAdd($2);
            }

            s = s.substring(i - 64);
            $9 = s.length;
        }

        $1[0] = 0;
        $1[1] = 0;
        $1[2] = 0;
        $1[3] = 0;
        $1[4] = 0;
        $1[5] = 0;
        $1[6] = 0;
        $1[7] = 0;
        $1[8] = 0;
        $1[9] = 0;
        $1[10] = 0;
        $1[11] = 0;
        $1[12] = 0;
        $1[13] = 0;
        $1[14] = 0;
        $1[15] = 0;

        for (i = 0; i < $9; i++) {
            I = i % 4;
            if (I === 0)
                $1[i >> 2] = s.charCodeAt(i);
            else
                $1[i >> 2] |= s.charCodeAt(i) << $5[I];
        }
        $1[i >> 2] |= $3[i % 4];

        if (i > 55) {
            if ($7) md5cycleAdd($1);
            else {
                md5cycle($1);
                $7 = true;
            }

            return md5cycleAdd([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, $8 << 3, 0]);
        }

        $1[14] = $8 << 3;

        if ($7) md5cycleAdd($1);
        else md5cycle($1);
    }

    function getBlocks(s) {
        for (var i = 16; i--;) {
            var I = i << 2;
            $2[i] = s.charCodeAt(I) + (s.charCodeAt(I + 1) << 8) + (s.charCodeAt(I + 2) << 16) + (s.charCodeAt(I + 3) << 24);
        }
    }

    function md5(data, ascii, arrayOutput) {
        md5_update(ascii ? data : encode(data));

        var tmp = $6[0];
        $0[1] = $4[tmp & 15];
        $0[0] = $4[(tmp >>= 4) & 15];
        $0[3] = $4[(tmp >>= 4) & 15];
        $0[2] = $4[(tmp >>= 4) & 15];
        $0[5] = $4[(tmp >>= 4) & 15];
        $0[4] = $4[(tmp >>= 4) & 15];
        $0[7] = $4[(tmp >>= 4) & 15];
        $0[6] = $4[(tmp >>= 4) & 15];

        tmp = $6[1];
        $0[9] = $4[tmp & 15];
        $0[8] = $4[(tmp >>= 4) & 15];
        $0[11] = $4[(tmp >>= 4) & 15];
        $0[10] = $4[(tmp >>= 4) & 15];
        $0[13] = $4[(tmp >>= 4) & 15];
        $0[12] = $4[(tmp >>= 4) & 15];
        $0[15] = $4[(tmp >>= 4) & 15];
        $0[14] = $4[(tmp >>= 4) & 15];

        tmp = $6[2];
        $0[17] = $4[tmp & 15];
        $0[16] = $4[(tmp >>= 4) & 15];
        $0[19] = $4[(tmp >>= 4) & 15];
        $0[18] = $4[(tmp >>= 4) & 15];
        $0[21] = $4[(tmp >>= 4) & 15];
        $0[20] = $4[(tmp >>= 4) & 15];
        $0[23] = $4[(tmp >>= 4) & 15];
        $0[22] = $4[(tmp >>= 4) & 15];

        tmp = $6[3];
        $0[25] = $4[tmp & 15];
        $0[24] = $4[(tmp >>= 4) & 15];
        $0[27] = $4[(tmp >>= 4) & 15];
        $0[26] = $4[(tmp >>= 4) & 15];
        $0[29] = $4[(tmp >>= 4) & 15];
        $0[28] = $4[(tmp >>= 4) & 15];
        $0[31] = $4[(tmp >>= 4) & 15];
        $0[30] = $4[(tmp >>= 4) & 15];

        return arrayOutput ? $0 : $0.join("");
    }

    function R(q, a, b, x, s1, s2, t) {
        a += q + x + t;
        return ((a << s1 | a >>> s2) + b) << 0;
    }

    function md5cycle(k) {
        md5_rounds(0, 0, 0, 0, k);

        $6[0] = (BUF[0] + 1732584193) << 0;
        $6[1] = (BUF[1] - 271733879) << 0;
        $6[2] = (BUF[2] - 1732584194) << 0;
        $6[3] = (BUF[3] + 271733878) << 0;
    }

    function md5cycleAdd(k) {
        md5_rounds($6[0], $6[1], $6[2], $6[3], k);

        $6[0] = (BUF[0] + $6[0]) << 0;
        $6[1] = (BUF[1] + $6[1]) << 0;
        $6[2] = (BUF[2] + $6[2]) << 0;
        $6[3] = (BUF[3] + $6[3]) << 0;
    }

    function md5_rounds(a, b, c, d, k) {
        var bc, da;

        if ($7) {
            a = R(((c ^ d) & b) ^ d, a, b, k[0], 7, 25, -680876936);
            d = R(((b ^ c) & a) ^ c, d, a, k[1], 12, 20, -389564586);
            c = R(((a ^ b) & d) ^ b, c, d, k[2], 17, 15, 606105819);
            b = R(((d ^ a) & c) ^ a, b, c, k[3], 22, 10, -1044525330);
        } else {
            a = k[0] - 680876937;
            a = ((a << 7 | a >>> 25) - 271733879) << 0;
            d = k[1] - 117830708 + ((2004318071 & a) ^ -1732584194);
            d = ((d << 12 | d >>> 20) + a) << 0;
            c = k[2] - 1126478375 + (((a ^ -271733879) & d) ^ -271733879);
            c = ((c << 17 | c >>> 15) + d) << 0;
            b = k[3] - 1316259209 + (((d ^ a) & c) ^ a);
            b = ((b << 22 | b >>> 10) + c) << 0;
        }

        a = R(((c ^ d) & b) ^ d, a, b, k[4], 7, 25, -176418897);
        d = R(((b ^ c) & a) ^ c, d, a, k[5], 12, 20, 1200080426);
        c = R(((a ^ b) & d) ^ b, c, d, k[6], 17, 15, -1473231341);
        b = R(((d ^ a) & c) ^ a, b, c, k[7], 22, 10, -45705983);
        a = R(((c ^ d) & b) ^ d, a, b, k[8], 7, 25, 1770035416);
        d = R(((b ^ c) & a) ^ c, d, a, k[9], 12, 20, -1958414417);
        c = R(((a ^ b) & d) ^ b, c, d, k[10], 17, 15, -42063);
        b = R(((d ^ a) & c) ^ a, b, c, k[11], 22, 10, -1990404162);
        a = R(((c ^ d) & b) ^ d, a, b, k[12], 7, 25, 1804603682);
        d = R(((b ^ c) & a) ^ c, d, a, k[13], 12, 20, -40341101);
        c = R(((a ^ b) & d) ^ b, c, d, k[14], 17, 15, -1502002290);
        b = R(((d ^ a) & c) ^ a, b, c, k[15], 22, 10, 1236535329);

        a = R(((b ^ c) & d) ^ c, a, b, k[1], 5, 27, -165796510);
        d = R(((a ^ b) & c) ^ b, d, a, k[6], 9, 23, -1069501632);
        c = R(((d ^ a) & b) ^ a, c, d, k[11], 14, 18, 643717713);
        b = R(((c ^ d) & a) ^ d, b, c, k[0], 20, 12, -373897302);
        a = R(((b ^ c) & d) ^ c, a, b, k[5], 5, 27, -701558691);
        d = R(((a ^ b) & c) ^ b, d, a, k[10], 9, 23, 38016083);
        c = R(((d ^ a) & b) ^ a, c, d, k[15], 14, 18, -660478335);
        b = R(((c ^ d) & a) ^ d, b, c, k[4], 20, 12, -405537848);
        a = R(((b ^ c) & d) ^ c, a, b, k[9], 5, 27, 568446438);
        d = R(((a ^ b) & c) ^ b, d, a, k[14], 9, 23, -1019803690);
        c = R(((d ^ a) & b) ^ a, c, d, k[3], 14, 18, -187363961);
        b = R(((c ^ d) & a) ^ d, b, c, k[8], 20, 12, 1163531501);
        a = R(((b ^ c) & d) ^ c, a, b, k[13], 5, 27, -1444681467);
        d = R(((a ^ b) & c) ^ b, d, a, k[2], 9, 23, -51403784);
        c = R(((d ^ a) & b) ^ a, c, d, k[7], 14, 18, 1735328473);
        b = R(((c ^ d) & a) ^ d, b, c, k[12], 20, 12, -1926607734);

        bc = b ^ c;
        a = R(bc ^ d, a, b, k[5], 4, 28, -378558);
        d = R(bc ^ a, d, a, k[8], 11, 21, -2022574463);
        da = d ^ a;
        c = R(da ^ b, c, d, k[11], 16, 16, 1839030562);
        b = R(da ^ c, b, c, k[14], 23, 9, -35309556);
        bc = b ^ c;
        a = R(bc ^ d, a, b, k[1], 4, 28, -1530992060);
        d = R(bc ^ a, d, a, k[4], 11, 21, 1272893353);
        da = d ^ a;
        c = R(da ^ b, c, d, k[7], 16, 16, -155497632);
        b = R(da ^ c, b, c, k[10], 23, 9, -1094730640);
        bc = b ^ c;
        a = R(bc ^ d, a, b, k[13], 4, 28, 681279174);
        d = R(bc ^ a, d, a, k[0], 11, 21, -358537222);
        da = d ^ a;
        c = R(da ^ b, c, d, k[3], 16, 16, -722521979);
        b = R(da ^ c, b, c, k[6], 23, 9, 76029189);
        bc = b ^ c;
        a = R(bc ^ d, a, b, k[9], 4, 28, -640364487);
        d = R(bc ^ a, d, a, k[12], 11, 21, -421815835);
        da = d ^ a;
        c = R(da ^ b, c, d, k[15], 16, 16, 530742520);
        b = R(da ^ c, b, c, k[2], 23, 9, -995338651);

        a = R(c ^ (b | ~d), a, b, k[0], 6, 26, -198630844);
        d = R(b ^ (a | ~c), d, a, k[7], 10, 22, 1126891415);
        c = R(a ^ (d | ~b), c, d, k[14], 15, 17, -1416354905);
        b = R(d ^ (c | ~a), b, c, k[5], 21, 11, -57434055);
        a = R(c ^ (b | ~d), a, b, k[12], 6, 26, 1700485571);
        d = R(b ^ (a | ~c), d, a, k[3], 10, 22, -1894986606);
        c = R(a ^ (d | ~b), c, d, k[10], 15, 17, -1051523);
        b = R(d ^ (c | ~a), b, c, k[1], 21, 11, -2054922799);
        a = R(c ^ (b | ~d), a, b, k[8], 6, 26, 1873313359);
        d = R(b ^ (a | ~c), d, a, k[15], 10, 22, -30611744);
        c = R(a ^ (d | ~b), c, d, k[6], 15, 17, -1560198380);
        b = R(d ^ (c | ~a), b, c, k[13], 21, 11, 1309151649);
        a = R(c ^ (b | ~d), a, b, k[4], 6, 26, -145523070);
        d = R(b ^ (a | ~c), d, a, k[11], 10, 22, -1120210379);
        c = R(a ^ (d | ~b), c, d, k[2], 15, 17, 718787259);
        b = R(d ^ (c | ~a), b, c, k[9], 21, 11, -343485551);

        BUF[0] = a;
        BUF[1] = b;
        BUF[2] = c;
        BUF[3] = d;
    }

    g.md5 = g.md5 || md5;
}(window);

N2D('NextendCSS', function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @constructor
     */
    function NextendCSS() {
        this.style = '';
    }

    NextendCSS.prototype.add = function (css) {
        var body = document.body || document.getElementsByTagName('body')[0],
            style = document.createElement('style');

        body.appendChild(style);

        style.type = 'text/css';
        if (style.styleSheet) {
            style.styleSheet.cssText = css;
        } else {
            style.appendChild(document.createTextNode(css));
        }
    };

    NextendCSS.prototype.deleteRule = function (selectorText) {
        var selectorText1 = selectorText.toLowerCase();
        var selectorText2 = selectorText1.replace('.', '\\.');
        for (var j = document.styleSheets.length - 1; j >= 0; j--) {
            var rules = this._getRulesArray(j);
            for (var i = 0; rules && i < rules.length; i++) {
                if (rules[i].selectorText) {
                    var lo = rules[i].selectorText.toLowerCase();
                    if ((lo == selectorText1) || (lo == selectorText2)) {
                        if (document.styleSheets[j].cssRules) {
                            document.styleSheets[j].deleteRule(i);
                        } else {
                            document.styleSheets[j].removeRule(i);
                        }
                    }
                }
            }
        }
        return (null);
    };

    NextendCSS.prototype._getRulesArray = function (i) {
        var crossrule = null;
        try {
            if (document.styleSheets[i].cssRules)
                crossrule = document.styleSheets[i].cssRules;
            else if (document.styleSheets[i].rules)
                crossrule = document.styleSheets[i].rules;
        } catch (e) {
        }
        return (crossrule);
    };

    /**
     * @type {NextendCSS}
     */
    window.nextend.css = new NextendCSS();

    return window.nextend.css;
});

N2D('ImageHelper', function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param parameters
     * @param openLightbox
     * @param openMultipleLightbox
     * @param openFoldersLightbox
     * @constructor
     */
    function ImageHelper(parameters, openLightbox, openMultipleLightbox, openFoldersLightbox) {
        ImageHelper.prototype.openLightbox = openLightbox;
        ImageHelper.prototype.openMultipleLightbox = openMultipleLightbox;
        ImageHelper.prototype.openFoldersLightbox = openFoldersLightbox;
        nextend.imageHelper = this;
        this.parameters = $.extend({
            siteKeywords: [],
            imageUrls: [],
            wordpressUrl: '',
            placeholderImage: '',
            placeholderRepeatedImage: '',
            protocolRelative: 1
        }, parameters);
    }

    ImageHelper.prototype.protocolRelative = function (image) {
        if (this.parameters.protocolRelative) {
            return image.replace(/^http(s)?:\/\//, '//');
        }
        return image;
    };


    ImageHelper.prototype.make = function (image) {
        return this.dynamic(image);
    };

    ImageHelper.prototype.dynamic = function (image) {
        var imageUrls = this.parameters.imageUrls,
            keywords = this.parameters.siteKeywords,
            _image = this.protocolRelative(image);
        for (var i = 0; i < keywords.length; i++) {
            if (_image.indexOf(imageUrls[i]) === 0) {
                image = keywords[i] + _image.slice(imageUrls[i].length);
                break;
            }
        }
        return image;
    };

    ImageHelper.prototype.fixed = function (image) {
        var imageUrls = this.parameters.imageUrls,
            keywords = this.parameters.siteKeywords;
        for (var i = 0; i < keywords.length; i++) {
            if (image.indexOf(keywords[i]) === 0) {
                image = imageUrls[i] + image.slice(keywords[i].length);
                break;
            }
        }
        return image;
    };

    ImageHelper.prototype.openLightbox = function (callback) {

    };

    ImageHelper.prototype.openMultipleLightbox = function (callback) {
    };

    ImageHelper.prototype.openFoldersLightbox = function (callback) {
    };

    ImageHelper.prototype.getPlaceholder = function () {
        return this.fixed(this.parameters.placeholderImage);
    };

    ImageHelper.prototype.getRepeatedPlaceholder = function () {
        return this.fixed(this.parameters.placeholderRepeatedImage);
    };

    return ImageHelper;
});
N2D('NextendModal', function ($, undefined) {

    var counter = 0;

    /**
     * @memberOf N2Classes
     *
     * @param panes
     * @param show
     * @param args
     * @constructor
     */
    function NextendModal(panes, show, args) {
        this.inited = false;
        this.currentPane = null;
        this.customClass = '';
        this.$ = $(this);
        this.counter = counter++;

        this.panes = panes;

        if (show) {
            this.show(null, args);
        }

    }

    NextendModal.prototype.setCustomClass = function (customClass) {
        this.customClass = customClass;
    };

    NextendModal.prototype.lateInit = function () {
        if (!this.inited) {

            for (var k in this.panes) {
                this.panes[k] = $.extend({
                    customClass: '',
                    fit: false,
                    fitX: true,
                    overflow: 'hidden',
                    size: false,
                    back: false,
                    close: true,
                    controlsClass: '',
                    controls: [],
                    fn: {}
                }, this.panes[k]);
            }

            var stopClick = false;
            this.modal = $('<div class="n2-modal ' + this.customClass + '"/>').css('opacity', 0)
                .on('click', $.proxy(function (e) {
                    if (stopClick == false) {
                        if (!this.close.hasClass('n2-hidden') && $(e.target).closest('.n2-notification-center-modal').length == 0) {
                            this.hide(e);
                        }
                    }
                    stopClick = false;
                }, this));
            this.window = $('<div class="n2-modal-window n2-border-radius"/>')
                .on('click', function (e) {
                    stopClick = true;
                }).appendTo(this.modal);
            this.notificationStack = new N2Classes.NotificationStackModal(this.modal);

            var titleContainer = $('<div class="n2-modal-title n2-content-box-title-bg"/>')
                .appendTo(this.window);

            this.title = $('<div class="n2-h2 n2-ucf"/>').appendTo(titleContainer);
            this.back = $('<i class="n2-i n2-i-a-back"/>')
                .on('click', $.proxy(this.goBackButton, this))
                .appendTo(titleContainer);
            this.close = $('<i class="n2-i n2-i-a-deletes"/>')
                .on('click', $.proxy(this.hide, this))
                .appendTo(titleContainer);

            this.content = $('<div class="n2-modal-content"/>').appendTo(this.window);
            this.controls = $('<div class="n2-table n2-table-fixed n2-table-auto"/>');

            $('<div class="n2-modal-controls"/>')
                .append(this.controls)
                .appendTo(this.window);

            this.inited = true;
        }
    };

    NextendModal.prototype.show = function (paneId, args) {
        this.lateInit();
        this.notificationStack.enableStack();
        if (typeof paneId === 'undefined' || !paneId) {
            paneId = 'zero';
        }

        N2Classes.WindowManager.get().addWindow("modal");
        N2Classes.Esc.add($.proxy(function () {
            if (!this.close.hasClass('n2-hidden')) {
                this.hide('esc');
                return true;
            }
            return false;
        }, this));

        this.loadPane(paneId, false, true, args);

        NextendTween.fromTo(this.modal, 0.3, {
            opacity: 0
        }, {
            opacity: 1,
            ease: 'easeOutCubic'
        });
    };

    NextendModal.prototype.hide = function (e) {
        $(window).off('.n2-modal-' + this.counter);
        this.notificationStack.popStack();
        N2Classes.WindowManager.get().removeWindow();
        if (arguments.length > 0 && e != 'esc') {
            N2Classes.Esc.pop();
        }
        this.apply('hide');
        this.apply('destroy');
        this.currentPane = null;
        this.modal.detach();

        $(document).off('keyup.n2-esc-modal');
    };

    NextendModal.prototype.destroy = function () {
        this.modal.remove();
    };

    NextendModal.prototype.loadPane = function (id, backward, isShow, args) {
        var end = $.proxy(function () {
            var pane = this.panes[id];
            this.currentPane = pane;

            if (pane.title !== false) {
                this.title.html(pane.title);
            }

            if (pane.back === false) {
                this.back.addClass('n2-hidden');
            } else {
                this.back.removeClass('n2-hidden');
            }

            if (pane.close === false) {
                this.close.addClass('n2-hidden');
            } else {
                this.close.removeClass('n2-hidden');
            }

            this.content.find('> *').detach();
            this.content.append(pane.content);


            var hasControls = false;
            var tr = $('<div class="n2-tr" />');
            var i = 0;
            for (; i < pane.controls.length; i++) {
                $('<div class="n2-td"/>')
                    .addClass('n2-modal-controls-' + i)
                    .html(pane.controls[i])
                    .appendTo(tr);
                hasControls = true;
            }

            tr.addClass('n2-modal-controls-' + i);
            this.controls.html(tr);
            this.controls.attr('class', 'n2-table n2-table-fixed n2-table-auto ' + pane.controlsClass);


            if (typeof isShow == 'undefined' || !isShow) {
                NextendTween.fromTo(this.window, 0.3, {
                    x: backward ? -2000 : 2000
                }, {
                    x: 0,
                    ease: 'easeOutCubic'
                });
            }

            this.modal.appendTo('#n2-admin');

            if (pane.fit) {
                var $w = $(window),
                    margin = 40,
                    resize = $.proxy(function () {
                        var w = $w.width() - 2 * margin,
                            h = $w.height() - 2 * margin;

                        if (!pane.fitX) {
                            w = pane.size[0];
                        }
                        this.window.css({
                            width: w,
                            height: h,
                            marginLeft: w / -2,
                            marginTop: h / -2
                        });

                        this.content.css({
                            height: h - 60 - (hasControls ? this.controls.parent().outerHeight(true) : 0),
                            overflow: pane.overflow
                        });
                    }, this);
                resize();
                $w.on('resize.n2-modal-' + this.counter, resize);
            } else if (pane.size !== false) {
                this.window.css({
                    width: pane.size[0],
                    height: pane.size[1],
                    marginLeft: pane.size[0] / -2,
                    marginTop: pane.size[1] / -2
                });

                this.content.css({
                    height: pane.size[1] - 60 - (hasControls ? this.controls.parent().outerHeight(true) : 0),
                    overflow: pane.overflow
                });

            }

            this.apply('show', args);

        }, this);

        if (this.currentPane !== null) {
            this.apply('destroy');
            NextendTween.to(this.window, 0.3, {
                x: backward ? 2000 : -2000,
                onComplete: end,
                ease: 'easeOutCubic'
            });
        } else {
            end();
        }

    };

    NextendModal.prototype.trigger = function (event, args) {
        this.$.trigger(event, args);
    };

    NextendModal.prototype.on = function (event, fn) {
        this.$.on(event, fn);
    };

    NextendModal.prototype.one = function (event, fn) {
        this.$.one(event, fn);
    };

    NextendModal.prototype.off = function (event, fn) {
        this.$.off(event, fn);
    };

    NextendModal.prototype.goBackButton = function () {
        var args = null;
        if (typeof this.goBackArgs !== null) {
            args = this.goBackArgs;
            this.goBackArgs = null;
        }
        this.goBack(args);
    };

    NextendModal.prototype.goBack = function (args) {
        if (this.apply('goBack', args)) {
            this.loadPane(this.currentPane.back, true, false, args);
        }
    };

    NextendModal.prototype.apply = function (event, args) {
        if (typeof this.currentPane.fn[event] !== 'undefined') {
            return this.currentPane.fn[event].apply(this, args);
        }
        return true;
    };

    NextendModal.prototype.createInput = function (label, id) {
        var style = '';
        if (arguments.length == 3) {
            style = arguments[2];
        }
        return $('<div class="n2-form-element-mixed"><div class="n2-mixed-group"><div class="n2-mixed-label"><label for="' + id + '">' + label + '</label></div><div class="n2-mixed-element"><div class="n2-form-element-text n2-border-radius"><input type="text" id="' + id + '" value="" class="n2-h5" autocomplete="off" style="' + style + '"></div></div></div></div>');
    };

    NextendModal.prototype.createInputUnit = function (label, id, unit) {
        var style = '';
        if (arguments.length == 4) {
            style = arguments[3];
        }
        return $('<div class="n2-form-element-mixed"><div class="n2-mixed-group"><div class="n2-mixed-label"><label for="' + id + '">' + label + '</label></div><div class="n2-mixed-element"><div class="n2-form-element-text n2-border-radius"><input type="text" id="' + id + '" value="" class="n2-h5" autocomplete="off" style="' + style + '"><div class="n2-text-unit n2-h5 n2-uc">' + unit + '</div></div></div></div></div>');
    };

    NextendModal.prototype.createInputSub = function (label, id, sub) {
        var style = '';
        if (arguments.length == 4) {
            style = arguments[3];
        }
        return $('<div class="n2-form-element-mixed"><div class="n2-mixed-group"><div class="n2-mixed-label"><label for="' + id + '">' + label + '</label></div><div class="n2-mixed-element"><div class="n2-form-element-text n2-border-radius"><div class="n2-text-sub-label n2-h5 n2-uc">' + sub + '</div><input type="text" id="' + id + '" value="" class="n2-h5" autocomplete="off" style="' + style + '"></div></div></div></div>');
    };

    NextendModal.prototype.createTextarea = function (label, id) {
        var style = '';
        if (arguments.length == 3) {
            style = arguments[2];
        }
        return $('<div class="n2-form-element-mixed"><div class="n2-mixed-group"><div class="n2-mixed-label"><label for="' + id + '">' + label + '</label></div><div class="n2-mixed-element"><div class="n2-form-element-textarea n2-border-radius"><textarea id="' + id + '" class="n2-h5" autocomplete="off" style="resize:none;' + style + '"></textarea></div></div></div></div>');
    };


    NextendModal.prototype.createSelect = function (label, id, values) {
        var style = '';
        if (arguments.length == 4) {
            style = arguments[3];
        }
        $group = $('<div class="n2-form-element-mixed"><div class="n2-mixed-group "><div class="n2-mixed-label"><label for="' + id + '">' + label + '</label></div><div class="n2-mixed-element"><div class="n2-form-element-list" style=""><select id="' + id + '" autocomplete="off" style="' + style + '"></select></div></div></div></div>');
        $select = $group.find('select');

        for (var k in values) {
            $('<option value="' + k + '"></option>').text(values[k]).appendTo($select);
        }
        $select.prop('selectedIndex', 0);

        return $group;
    };


    NextendModal.prototype.createHeading = function (title) {
        return $('<h3 class="n2-h3">' + title + '</h3>');
    };
    NextendModal.prototype.createSubHeading = function (title) {
        return $('<h3 class="n2-h4">' + title + '</h3>');
    };

    NextendModal.prototype.createCenteredHeading = function (title) {
        return $('<h3 class="n2-h3 n2-center">' + title + '</h3>');
    };
    NextendModal.prototype.createCenteredSubHeading = function (title) {
        return $('<h3 class="n2-h4 n2-center">' + title + '</h3>');
    };

    NextendModal.prototype.createResult = function () {
        return $('<div class="n2-result"></div>');
    };

    NextendModal.prototype.createTable = function (data, style) {
        var table = $('<table class="n2-table-fancy"/>');
        for (var j = 0; j < data.length; j++) {
            var tr = $('<tr />').appendTo(table);
            for (var i = 0; i < data[j].length; i++) {
                tr.append($('<td style="' + style[i] + '"/>').append(data[j][i]));
            }
        }
        return table;
    };

    NextendModal.prototype.createTableWrap = function () {
        return $('<div class="n2-table-fancy-wrap" style="overflow:auto;height:196px;" />');
    };

    NextendModal.prototype.createImageRadio = function (options) {

        var wrapper = $('<div class="n2-modal-radio" />'),
            input = $('<input type="hidden" value="' + options[0].key + '"/>').appendTo(wrapper);

        for (var i = 0; i < options.length; i++) {
            var url = "'" + nextend.imageHelper.fixed(options[i].image) + "'";
            wrapper.append('<div class="n2-modal-radio-option" data-key="' + options[i].key + '" style="background-image: url(' + url + ')"><div class="n2-h4">' + options[i].name + '</div></div>')
        }

        var options = wrapper.find('.n2-modal-radio-option');
        options.eq(0).addClass('n2-active');

        options.on('click', function (e) {
            options.removeClass('n2-active');
            var option = $(e.currentTarget);
            option.addClass('n2-active');
            input.val(option.data('key'));
        });

        return wrapper;
    };

    NextendModal.settings = function (title, url) {
        new N2Classes.NextendModal({
            zero: {
                size: [
                    1300,
                    700
                ],
                title: title,
                content: '<iframe src="' + url + '" width="1300" height="640" frameborder="0" style="margin:0 -20px -20px -20px;"></iframe>'
            }
        }, true);
    };

    NextendModal.documentation = function (title, url) {
        new N2Classes.NextendModal({
            zero: {
                size: [
                    760,
                    700
                ],
                title: title,
                content: '<iframe src="' + url + '" width="760" height="640" frameborder="0" style="margin:0 -20px -20px -20px;"></iframe>'
            }
        }, true);
    };

    NextendModal.newFullWindow = function (url, id) {
        var params = [
            'height=' + screen.height,
            'width=' + screen.width,
            'fullscreen=yes'
        ].join(',');

        var popup = window.open(url, id, params);
        popup.moveTo(0, 0);
        return popup;
    };

    NextendModal.deleteModal = function (identifier, instanceName, callback) {
        if ($.jStorage.get('n2-delete-' + identifier, false)) {
            callback();
            return true;
        }
        new N2Classes.NextendModal({
            zero: {
                size: [
                    500,
                    190
                ],
                title: n2_('Delete'),
                back: false,
                close: true,
                content: '',
                controls: ['<a href="#" class="n2-button n2-button-normal n2-button-l n2-radius-s n2-button-grey n2-uc n2-h4">' + n2_('Cancel') + '</a>', '<div class="n2-button n2-button-with-actions n2-button-l n2-radius-s n2-button-red"><a href="#" class="n2-button-inner n2-uc n2-h4">' + n2_('Delete') + '</a><div class="n2-button-menu-open"><i class="n2-i n2-i-buttonarrow"></i><div class="n2-button-menu"><div class="n2-button-menu-inner n2-border-radius"><a href="#" class="n2-h4">' + n2_('Delete and never ask for confirmation again') + '</a></div></div></div></div>'],
                fn: {
                    show: function () {
                        this.createCenteredSubHeading(n2_('Are you sure you want to delete?')).appendTo(this.content);
                        this.controls.find('.n2-button-grey')
                            .on('click', $.proxy(function (e) {
                                e.preventDefault();
                                this.hide(e);
                            }, this));
                        this.controls.find('.n2-button-red a')
                            .on('click', $.proxy(function (e) {
                                e.preventDefault();
                                callback();
                                this.hide(e);
                            }, this));

                        this.controls.find('.n2-button-red .n2-button-menu-inner a')
                            .on('click', $.proxy(function (e) {
                                e.preventDefault();
                                $.jStorage.set('n2-delete-' + identifier, true);
                            }, this));

                        this.controls.find(".n2-button-menu-open").n2opener();

                    },
                    destroy: function () {
                        this.destroy();
                    }
                }
            }
        }, true);
        return false;
    };

    NextendModal.deleteModalLink = function (element, identifier, instanceName) {
        N2Classes.NextendModal.deleteModal(identifier, instanceName, function () {
            window.location.href = $(element).attr('href');
        });
        return false;
    };

    NextendModal.SafeHTML = function (title, html) {

        var modal = new N2Classes.NextendModal({
            zero: {
                fit: true,
                size: [
                    1300,
                    700
                ],
                title: title,
                content: ''
            }
        }, true);

        modal.content.removeClass('n2-modal-content').css('padding', '0 20px 20px');
        var $html = $(html.replace(/document\.write/g, 'n2Write')),
            $currentNode;
        window.n2Write = $.proxy(function (buffer) {
            $('<span />').html(buffer).appendTo(modal.content);
        }, this);
        $html.each($.proxy(function (i, el) {
            $currentNode = $(el);
            $currentNode.appendTo(modal.content);
        }, this));
        delete window.n2Write;
    };

    return NextendModal;
});
N2D('NextendSimpleModal', function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param html
     * @param options
     * @constructor
     */
    function NextendSimpleModal(html, options) {
        this.$ = $(this);
        this.options = $.extend({
            'class': ''
        }, options);
        this.modal = $('<div class="n2-modal n2-modal-simple"/>').addClass(this.options.class).css({
            display: 'none'
        }).appendTo('#n2-admin');

        $('<i class="n2-i n2-i-a-deletes"/>')
            .on('click', $.proxy(this.hide, this))
            .appendTo(this.modal);

        this.window = $('<div class="n2-modal-window"/>')
            .on('click', function (e) {
                e.stopPropagation();
            })
            .appendTo(this.modal);
        this.notificationStack = new N2Classes.NotificationStackModal(this.modal);
        this.content = $(html).appendTo(this.window);
    }

    NextendSimpleModal.prototype.resize = function () {
        this.window.width(this.modal.width());
        this.window.height(this.modal.height());
    };

    NextendSimpleModal.prototype.show = function () {
        $('body').addClass('n2-modal-active');
        this.modal.css('display', 'block');
        this.resize();
        $(window).on('resize.n2-simple-modal', $.proxy(this.resize, this));
        this.notificationStack.enableStack();

        N2Classes.Esc.add($.proxy(function () {
            this.hide('esc');
            return true;
        }, this));
    };

    NextendSimpleModal.prototype.hide = function (e) {
        this.notificationStack.popStack();
        if (arguments.length > 0 && e != 'esc') {
            N2Classes.Esc.pop();
        }
        this.modal.css('display', 'none');
        $('body').removeClass('n2-modal-active');
        $(document).off('keyup.n2-esc-modal');
        $(window).off('.n2-simple-modal');
        this.modal.trigger('ModalHide');
    };

    return NextendSimpleModal;
});
// Spectrum Colorpicker v1.0.9
// https://github.com/bgrins/spectrum
// Author: Brian Grinstead
// License: MIT

N2D('Spectrum', function ($, undefined) {
    var tinycolor = null;
    var defaultOpts = {

            // Events
            beforeShow: noop,
            move: noop,
            change: noop,
            show: noop,
            hide: noop,

            // Options
            color: false,
            flat: false,
            showInput: false,
            showButtons: true,
            clickoutFiresChange: false,
            showInitial: false,
            showPalette: false,
            showPaletteOnly: false,
            showSelectionPalette: true,
            localStorageKey: false,
            maxSelectionSize: 7,
            cancelText: "cancel",
            chooseText: "choose",
            preferredFormat: false,
            className: "",
            showAlpha: false,
            theme: "n2-sp-light",
            palette: ['fff', '000'],
            selectionPalette: [],
            disabled: false
        },
        spectrums = [],
        IE = !!/msie/i.exec(window.navigator.userAgent),
        rgbaSupport = (function () {
            function contains(str, substr) {
                return !!~('' + str).indexOf(substr);
            }

            var elem = document.createElement('div');
            var style = elem.style;
            style.cssText = 'background-color:rgba(0,0,0,.5)';
            return contains(style.backgroundColor, 'rgba') || contains(style.backgroundColor, 'hsla');
        })(),
        replaceInput = [
            "<div class='n2-sp-replacer'>",
            "<div class='n2-sp-preview'><div class='n2-sp-preview-inner'></div></div>",
            "<div class='n2-sp-dd'>&#9650;</div>",
            "</div>"
        ].join(''),
        markup = (function () {

            // IE does not support gradients with multiple stops, so we need to simulate
            //  that for the rainbow slider with 8 divs that each have a single gradient
            var gradientFix = "";
            if (IE) {
                for (var i = 1; i <= 6; i++) {
                    gradientFix += "<div class='n2-sp-" + i + "'></div>";
                }
            }

            return [
                "<div class='n2-sp-container'>",
                "<div class='n2-sp-palette-container'>",
                "<div class='n2-sp-palette n2-sp-thumb n2-sp-cf'></div>",
                "</div>",
                "<div class='n2-sp-picker-container'>",
                "<div class='n2-sp-top n2-sp-cf'>",
                "<div class='n2-sp-fill'></div>",
                "<div class='n2-sp-top-inner'>",
                "<div class='n2-sp-color'>",
                "<div class='n2-sp-sat'>",
                "<div class='n2-sp-val'>",
                "<div class='n2-sp-dragger'></div>",
                "</div>",
                "</div>",
                "</div>",
                "<div class='n2-sp-hue'>",
                "<div class='n2-sp-slider'></div>",
                gradientFix,
                "</div>",
                "</div>",
                "<div class='n2-sp-alpha'><div class='n2-sp-alpha-inner'><div class='n2-sp-alpha-handle'></div></div></div>",
                "</div>",
                "<div class='n2-sp-input-container n2-sp-cf'>",
                "<input class='n2-sp-input' type='text' spellcheck='false'  />",
                "</div>",
                "<div class='n2-sp-initial n2-sp-thumb n2-sp-cf'></div>",
                "<div class='n2-sp-button-container n2-sp-cf'>",
                "<a class='n2-sp-cancel' href='#'></a>",
                "<button class='n2-sp-choose'></button>",
                "</div>",
                "</div>",
                "</div>"
            ].join("");
        })();

    function paletteTemplate(p, color, className) {
        var html = [];
        for (var i = 0; i < p.length; i++) {
            var tiny = tinycolor(p[i]);
            var c = tiny.toHsl().l < 0.5 ? "n2-sp-thumb-el n2-sp-thumb-dark" : "n2-sp-thumb-el n2-sp-thumb-light";
            c += (tinycolor.equals(color, p[i])) ? " n2-sp-thumb-active" : "";

            var swatchStyle = "background-color:" + tiny.toRgbString();
            html.push('<span title="' + tiny.toRgbString() + '" data-color="' + tiny.toRgbString() + '" class="' + c + '"><span class="n2-sp-thumb-inner" style="' + swatchStyle + ';" /></span>');
        }
        return "<div class='n2-sp-cf " + className + "'>" + html.join('') + "</div>";
    }

    function hideAll() {
        for (var i = 0; i < spectrums.length; i++) {
            if (spectrums[i]) {
                spectrums[i].hide();
            }
        }
    }

    function instanceOptions(o, callbackContext) {
        var opts = $.extend({}, defaultOpts, o);
        opts.callbacks = {
            'move': bind(opts.move, callbackContext),
            'change': bind(opts.change, callbackContext),
            'show': bind(opts.show, callbackContext),
            'hide': bind(opts.hide, callbackContext),
            'beforeShow': bind(opts.beforeShow, callbackContext)
        };

        return opts;
    }

    function spectrum(element, o) {

        var opts = instanceOptions(o, element),
            flat = opts.flat,
            showSelectionPalette = opts.showSelectionPalette,
            localStorageKey = opts.localStorageKey,
            theme = opts.theme,
            callbacks = opts.callbacks,
            resize = throttle(reflow, 10),
            visible = false,
            dragWidth = 0,
            dragHeight = 0,
            dragHelperHeight = 0,
            slideHeight = 0,
            slideWidth = 0,
            alphaWidth = 0,
            alphaSlideHelperWidth = 0,
            slideHelperHeight = 0,
            currentHue = 0,
            currentSaturation = 0,
            currentValue = 0,
            currentAlpha = 1,
            palette = opts.palette.slice(0),
            paletteArray = $.isArray(palette[0]) ? palette : [palette],
            selectionPalette = opts.selectionPalette.slice(0),
            draggingClass = "n2-sp-dragging";


        var doc = element.ownerDocument,
            body = doc.body,
            boundElement = $(element),
            disabled = false,
            container = $(markup, doc).addClass(theme),
            dragger = container.find(".n2-sp-color"),
            dragHelper = container.find(".n2-sp-dragger"),
            slider = container.find(".n2-sp-hue"),
            slideHelper = container.find(".n2-sp-slider"),
            alphaSliderInner = container.find(".n2-sp-alpha-inner"),
            alphaSlider = container.find(".n2-sp-alpha"),
            alphaSlideHelper = container.find(".n2-sp-alpha-handle"),
            textInput = container.find(".n2-sp-input"),
            paletteContainer = container.find(".n2-sp-palette"),
            initialColorContainer = container.find(".n2-sp-initial"),
            cancelButton = container.find(".n2-sp-cancel"),
            chooseButton = container.find(".n2-sp-choose"),
            isInput = boundElement.is("input"),
            shouldReplace = isInput && !flat,
            replacer = null,
            offsetElement = null,
            previewElement = null,
            initialColor = opts.color || (isInput && boundElement.val()),
            colorOnShow = false,
            preferredFormat = opts.preferredFormat,
            currentPreferredFormat = preferredFormat,
            clickoutFiresChange = !opts.showButtons || opts.clickoutFiresChange;

        container.on('mousedown', function (e) {
            N2Classes.WindowManager.get().setMouseDownArea('colorpicker', e);
        });


        function applyOptions(noReflow) {

            container.toggleClass("n2-sp-flat", flat);
            container.toggleClass("n2-sp-input-disabled", !opts.showInput);
            container.toggleClass("n2-sp-alpha-enabled", opts.showAlpha);
            container.toggleClass("n2-sp-buttons-disabled", !opts.showButtons || flat);
            container.toggleClass("n2-sp-palette-disabled", !opts.showPalette);
            container.toggleClass("n2-sp-palette-only", opts.showPaletteOnly);
            container.toggleClass("n2-sp-initial-disabled", !opts.showInitial);
            container.addClass(opts.className);

            if (typeof noReflow === 'undefined') {
                reflow();
            }
        }

        function initialize() {

            if (IE) {
                container.find("*:not(input)").attr("unselectable", "on");
            }

            var customReplace = boundElement.parent().find('.n2-sp-replacer');
            if (customReplace.length) {
                replacer = customReplace;
            } else {
                replacer = (shouldReplace) ? $(replaceInput).addClass(theme) : $([]);

                if (shouldReplace) {
                    //boundElement.hide().after(replacer);
                    boundElement.parent().after(replacer);
                }
            }
            offsetElement = (shouldReplace) ? replacer : boundElement;
            previewElement = replacer.find(".n2-sp-preview-inner");

            applyOptions(true);

            if (flat) {
                boundElement.parent().after(container).hide();
            }
            else {
                $(body).append(container.hide());
            }

            if (localStorageKey && window.localStorage) {

                try {
                    selectionPalette = window.localStorage[localStorageKey].split(";");
                }
                catch (e) {
                }
            }

            offsetElement.bind("click.spectrum touchstart.spectrum", function (e) {
                if (!disabled) {
                    toggle();
                }

                e.stopPropagation();

                if (!$(e.target).is("input")) {
                    e.preventDefault();
                }
            });

            if (boundElement.is(":disabled") || (opts.disabled === true)) {
                disable();
            }

            // Prevent clicks from bubbling up to document.  This would cause it to be hidden.
            container.click(stopPropagation);

            // Handle user typed input
            textInput.change(setFromTextInput);
            textInput.bind("paste", function () {
                setTimeout(setFromTextInput, 1);
            });
            textInput.keydown(function (e) {
                if (e.keyCode == 13) {
                    setFromTextInput();
                }
            });

            cancelButton.text(opts.cancelText);
            cancelButton.bind("click.spectrum", function (e) {
                e.stopPropagation();
                e.preventDefault();
                hide("cancel");
            });

            chooseButton.text(opts.chooseText);
            chooseButton.bind("click.spectrum", function (e) {
                e.stopPropagation();
                e.preventDefault();

                if (isValid()) {
                    updateOriginalInput(true);
                    hide();
                }
            });

            draggable(alphaSlider, function (dragX, dragY, e) {
                currentAlpha = (dragX / alphaWidth);
                if (e.shiftKey) {
                    currentAlpha = Math.round(currentAlpha * 10) / 10;
                }

                move();
            });

            draggable(slider, function (dragX, dragY) {
                currentHue = parseFloat(dragY / slideHeight);
                move();
            }, dragStart, dragStop);

            draggable(dragger, function (dragX, dragY) {
                currentSaturation = parseFloat(dragX / dragWidth);
                currentValue = parseFloat((dragHeight - dragY) / dragHeight);
                move();
            }, dragStart, dragStop);

            if (!!initialColor) {
                set(initialColor);

                // In case color was black - update the preview UI and set the format
                // since the set function will not run (default color is black).
                updateUI();
                currentPreferredFormat = preferredFormat || tinycolor(initialColor).format;

                addColorToSelectionPalette(initialColor);
            }
            else {
                updateUI();
            }

            if (flat) {
                show();
            }

            function palletElementClick(e) {
                if (e.data && e.data.ignore) {
                    set($(this).data("color"));
                    move();
                }
                else {
                    set($(this).data("color"));
                    updateOriginalInput(true);
                    move();
                    hide();
                }

                return false;
            }

            var paletteEvent = IE ? "mousedown.spectrum" : "click.spectrum touchstart.spectrum";
            paletteContainer.delegate(".n2-sp-thumb-el", paletteEvent, palletElementClick);
            initialColorContainer.delegate(".n2-sp-thumb-el:nth-child(1)", paletteEvent, {ignore: true}, palletElementClick);
        }

        function addColorToSelectionPalette(color) {
            if (showSelectionPalette) {
                var colorRgb = tinycolor(color).toRgbString();
                if ($.inArray(colorRgb, selectionPalette) === -1) {
                    selectionPalette.push(colorRgb);
                }

                if (localStorageKey && window.localStorage) {
                    try {
                        window.localStorage[localStorageKey] = selectionPalette.join(";");
                    }
                    catch (e) {
                    }
                }
            }
        }

        function getUniqueSelectionPalette() {
            var unique = [];
            var p = selectionPalette;
            var paletteLookup = {};
            var rgb;

            if (opts.showPalette) {

                for (var i = 0; i < paletteArray.length; i++) {
                    for (var j = 0; j < paletteArray[i].length; j++) {
                        rgb = tinycolor(paletteArray[i][j]).toRgbString();
                        paletteLookup[rgb] = true;
                    }
                }

                for (i = 0; i < p.length; i++) {
                    rgb = tinycolor(p[i]).toRgbString();

                    if (!paletteLookup.hasOwnProperty(rgb)) {
                        unique.push(p[i]);
                        paletteLookup[rgb] = true;
                    }
                }
            }

            return unique.reverse().slice(0, opts.maxSelectionSize);
        }

        function drawPalette() {

            var currentColor = get();

            var html = $.map(paletteArray, function (palette, i) {
                return paletteTemplate(palette, currentColor, "n2-sp-palette-row n2-sp-palette-row-" + i);
            });

            if (selectionPalette) {
                html.push(paletteTemplate(getUniqueSelectionPalette(), currentColor, "n2-sp-palette-row n2-sp-palette-row-selection"));
            }

            paletteContainer.html(html.join(""));
        }

        function drawInitial() {
            if (opts.showInitial) {
                var initial = colorOnShow;
                var current = get();
                initialColorContainer.html(paletteTemplate([initial, current], current, "n2-sp-palette-row-initial"));
            }
        }

        function dragStart() {
            if (dragHeight === 0 || dragWidth === 0 || slideHeight === 0) {
                reflow();
            }
            container.addClass(draggingClass);
        }

        function dragStop() {
            container.removeClass(draggingClass);
        }

        function setFromTextInput() {
            var tiny = tinycolor(textInput.val());
            if (tiny.ok) {
                set(tiny);
            }
            else {
                textInput.addClass("n2-sp-validation-error");
            }
        }

        function toggle() {
            if (visible) {
                hide();
            }
            else {
                show();
            }
        }

        function show() {
            if (visible) {
                reflow();
                return;
            }
            if (callbacks.beforeShow(get()) === false) return;

            hideAll();
            visible = true;

            $(doc).bind("click.spectrum", hide);
            $(window).bind("resize.spectrum", resize);
            replacer.addClass("n2-sp-active");
            container.show();

            if (opts.showPalette) {
                drawPalette();
            }
            reflow();
            updateUI();

            colorOnShow = get();

            drawInitial();
            callbacks.show(colorOnShow);
        }

        function hide(e) {

            // Return on right click
            if (e && e.type == "click" && e.button == 2) {
                return;
            }

            // Return if hiding is unnecessary
            if (!visible || flat) {
                return;
            }
            visible = false;

            $(doc).unbind("click.spectrum", hide);
            $(window).unbind("resize.spectrum", resize);

            replacer.removeClass("n2-sp-active");
            container.hide();

            var colorHasChanged = !tinycolor.equals(get(), colorOnShow);

            if (colorHasChanged) {
                if (clickoutFiresChange && e !== "cancel") {
                    updateOriginalInput(true);
                }
                else {
                    revert();
                }
            }

            callbacks.hide(get());
        }

        function revert() {
            set(colorOnShow, true);
        }

        function set(color, ignoreFormatChange) {
            if (tinycolor.equals(color, get())) {
                return;
            }

            var newColor = tinycolor(color);
            var newHsv = newColor.toHsv();

            currentHue = newHsv.h;
            currentSaturation = newHsv.s;
            currentValue = newHsv.v;
            currentAlpha = newHsv.a;

            updateUI();

            if (!ignoreFormatChange) {
                currentPreferredFormat = preferredFormat || newColor.format;
            }
        }

        function get() {
            return tinycolor.fromRatio({
                h: currentHue,
                s: currentSaturation,
                v: currentValue,
                a: Math.round(currentAlpha * 100) / 100
            });
        }

        function isValid() {
            return !textInput.hasClass("n2-sp-validation-error");
        }

        function move() {
            updateUI();

            callbacks.move(get());
        }

        function updateUI() {

            textInput.removeClass("n2-sp-validation-error");

            updateHelperLocations();

            // Update dragger background color (gradients take care of saturation and value).
            var flatColor = tinycolor({h: currentHue, s: "1.0", v: "1.0"});
            dragger.css("background-color", '#' + flatColor.toHexString());

            // Get a format that alpha will be included in (hex and names ignore alpha)
            var format = currentPreferredFormat;
            if (currentAlpha < 1) {
                if (format === "hex" || format === "name") {
                    format = "rgb";
                }
            }

            var realColor = get(),
                realHex = realColor.toHexString(),
                realRgb = realColor.toRgbString();


            // Update the replaced elements background color (with actual selected color)
            if (rgbaSupport || realColor.alpha === 1) {
                previewElement.css("background-color", realRgb);
            }
            else {
                previewElement.css("background-color", "transparent");
                previewElement.css("filter", realColor.toFilter());
            }

            if (opts.showAlpha) {
                var rgb = realColor.toRgb();
                rgb.a = 0;
                var realAlpha = tinycolor(rgb).toRgbString();
                var gradient = "linear-gradient(to right, " + realAlpha + ", " + realHex + ")";
                alphaSliderInner.css("background", gradient);
            }


            // Update the text entry input as it changes happen
            if (opts.showInput) {
                if (currentAlpha < 1) {
                    if (format === "hex" || format === "name") {
                        format = "rgb";
                    }
                }
                textInput.val(realColor.toString(format));
            }

            if (opts.showPalette) {
                drawPalette();
            }

            drawInitial();
        }

        function updateHelperLocations() {
            var s = currentSaturation;
            var v = currentValue;

            // Where to show the little circle in that displays your current selected color
            var dragX = s * dragWidth;
            var dragY = dragHeight - (v * dragHeight);
            dragX = Math.max(
                -dragHelperHeight,
                Math.min(dragWidth - dragHelperHeight, dragX - dragHelperHeight)
            );
            dragY = Math.max(
                -dragHelperHeight,
                Math.min(dragHeight - dragHelperHeight, dragY - dragHelperHeight)
            );
            dragHelper.css({
                "top": dragY,
                "left": dragX
            });

            var alphaX = currentAlpha * alphaWidth;
            alphaSlideHelper.css({
                "left": alphaX - (alphaSlideHelperWidth / 2)
            });

            // Where to show the bar that displays your current selected hue
            var slideY = (currentHue) * slideHeight;
            slideHelper.css({
                "top": slideY - slideHelperHeight
            });
        }

        function updateOriginalInput(fireCallback) {
            var color = get();

            if (isInput) {
                boundElement.val(color.toString(currentPreferredFormat)).change();
            }

            //var hasChanged = !tinycolor.equals(color, colorOnShow);
            var hasChanged = 1;

            colorOnShow = color;

            // Update the selection palette with the current color
            addColorToSelectionPalette(color);
            if (fireCallback && hasChanged) {
                callbacks.change(color);
            }
        }

        function reflow() {
            dragWidth = dragger.width();
            dragHeight = dragger.height();
            dragHelperHeight = dragHelper.height();
            slideWidth = slider.width();
            slideHeight = slider.height();
            slideHelperHeight = slideHelper.height();
            alphaWidth = alphaSlider.width();
            alphaSlideHelperWidth = alphaSlideHelper.width();

            if (!flat) {
                container.offset(getOffset(container, offsetElement.parent()));
            }

            updateHelperLocations();
        }

        function destroy() {
            boundElement.show();
            offsetElement.unbind("click.spectrum touchstart.spectrum");
            container.remove();
            replacer.remove();
            spectrums[spect.id] = null;
        }

        function option(optionName, optionValue) {
            if (optionName === undefined) {
                return $.extend({}, opts);
            }
            if (optionValue === undefined) {
                return opts[optionName];
            }

            opts[optionName] = optionValue;
            applyOptions();
        }

        function enable() {
            disabled = false;
            boundElement.attr("disabled", false);
            offsetElement.removeClass("n2-sp-disabled");
        }

        function disable() {
            hide();
            disabled = true;
            boundElement.attr("disabled", true);
            offsetElement.addClass("n2-sp-disabled");
        }

        initialize();

        var spect = {
            show: show,
            hide: hide,
            toggle: toggle,
            reflow: reflow,
            option: option,
            enable: enable,
            disable: disable,
            set: function (c) {
                set(c);
                updateOriginalInput();
            },
            get: get,
            destroy: destroy,
            container: container
        };

        spect.id = spectrums.push(spect) - 1;

        return spect;
    }

    /**
     * checkOffset - get the offset below/above and left/right element depending on screen position
     * Thanks https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.datepicker.js
     */
    function getOffset(picker, input) {
        var extraY = 0;
        var dpWidth = picker.outerWidth();
        var dpHeight = picker.outerHeight();
        var inputHeight = input.outerHeight();
        var doc = picker[0].ownerDocument;
        var docElem = doc.documentElement;
        var viewWidth = docElem.clientWidth + $(doc).scrollLeft();
        var viewHeight = docElem.clientHeight + $(doc).scrollTop();
        var offset = input.offset();
        offset.top += inputHeight + 3;

        offset.left -=
            Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ?
                Math.abs(offset.left + dpWidth - viewWidth) : 0);

        offset.top -=
            Math.min(offset.top, ((offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ?
                Math.abs(dpHeight + inputHeight + 6 - extraY) : extraY));

        return offset;
    }

    /**
     * noop - do nothing
     */
    function noop() {

    }

    /**
     * stopPropagation - makes the code only doing this a little easier to read in line
     */
    function stopPropagation(e) {
        e.stopPropagation();
    }

    /**
     * Create a function bound to a given object
     * Thanks to underscore.js
     */
    function bind(func, obj) {
        var slice = Array.prototype.slice;
        var args = slice.call(arguments, 2);
        return function () {
            return func.apply(obj, args.concat(slice.call(arguments)));
        };
    }

    /**
     * Lightweight drag helper.  Handles containment within the element, so that
     * when dragging, the x is within [0,element.width] and y is within [0,element.height]
     */
    function draggable(element, onmove, onstart, onstop) {
        onmove = onmove || function () {
        };
        onstart = onstart || function () {
        };
        onstop = onstop || function () {
        };
        var doc = element.ownerDocument || document;
        var dragging = false;
        var offset = {};
        var maxHeight = 0;
        var maxWidth = 0;
        var hasTouch = false;

        var duringDragEvents = {};
        duringDragEvents["selectstart"] = prevent;
        duringDragEvents["dragstart"] = prevent;
        duringDragEvents[(hasTouch ? "touchmove" : "mousemove")] = move;
        duringDragEvents[(hasTouch ? "touchend" : "mouseup")] = stop;

        function prevent(e) {
            if (e.stopPropagation) {
                e.stopPropagation();
            }
            if (e.preventDefault) {
                e.preventDefault();
            }
            e.returnValue = false;
        }

        function move(e) {
            if (dragging) {
                // Mouseup happened outside of window
                if (IE && document.documentMode < 9 && !e.button) {
                    return stop();
                }

                var touches = e.originalEvent.touches;
                var pageX = touches ? touches[0].pageX : e.pageX;
                var pageY = touches ? touches[0].pageY : e.pageY;

                var dragX = Math.max(0, Math.min(pageX - offset.left, maxWidth));
                var dragY = Math.max(0, Math.min(pageY - offset.top, maxHeight));

                if (hasTouch) {
                    // Stop scrolling in iOS
                    prevent(e);
                }

                onmove.apply(element, [dragX, dragY, e]);
            }
        }

        function start(e) {
            var rightclick = (e.which) ? (e.which == 3) : (e.button == 2);
            var touches = e.originalEvent.touches;

            if (!rightclick && !dragging) {
                if (onstart.apply(element, arguments) !== false) {
                    dragging = true;
                    maxHeight = $(element).height();
                    maxWidth = $(element).width();
                    offset = $(element).offset();

                    $(doc).bind(duringDragEvents);
                    $(doc.body).addClass("n2-sp-dragging");

                    if (!hasTouch) {
                        move(e);
                    }

                    prevent(e);
                }
            }
        }

        function stop() {
            if (dragging) {
                $(doc).unbind(duringDragEvents);
                $(doc.body).removeClass("n2-sp-dragging");
                onstop.apply(element, arguments);
            }
            dragging = false;
        }

        $(element).bind(hasTouch ? "touchstart" : "mousedown", start);
    }

    function throttle(func, wait, debounce) {
        var timeout;
        return function () {
            var context = this, args = arguments;
            var throttler = function () {
                timeout = null;
                func.apply(context, args);
            };
            if (debounce) clearTimeout(timeout);
            if (debounce || !timeout) timeout = setTimeout(throttler, wait);
        };
    }


    /**
     * Define a jQuery plugin
     */
    var dataID = "spectrum.id";
    $.fn.n2spectrum = function (opts, extra) {

        if (typeof opts == "string") {

            var returnValue = this;
            var args = Array.prototype.slice.call(arguments, 1);

            this.each(function () {
                var spect = spectrums[$(this).data(dataID)];
                if (spect) {

                    var method = spect[opts];
                    if (!method) {
                        throw new Error("Spectrum: no such method: '" + opts + "'");
                    }

                    if (opts == "get") {
                        returnValue = spect.get();
                    }
                    else if (opts == "container") {
                        returnValue = spect.container;
                    }
                    else if (opts == "option") {
                        returnValue = spect.option.apply(spect, args);
                    }
                    else if (opts == "destroy") {
                        spect.destroy();
                        $(this).removeData(dataID);
                    }
                    else {
                        method.apply(spect, args);
                    }
                }
            });

            return returnValue;
        }

        // Initializing a new instance of spectrum
        return this.n2spectrum("destroy").each(function () {
            var spect = spectrum(this, opts);
            $(this).data(dataID, spect.id);
        });
    };

    $.fn.n2spectrum.load = true;
    $.fn.n2spectrum.loadOpts = {};
    $.fn.n2spectrum.draggable = draggable;
    $.fn.n2spectrum.defaults = defaultOpts;

    $.n2spectrum = {};
    $.n2spectrum.localization = {};
    $.n2spectrum.palettes = {};

    // TinyColor.js - <https://github.com/bgrins/TinyColor> - 2011 Brian Grinstead - v0.5

    (function () {

        var trimLeft = /^[\s,#]+/,
            trimRight = /\s+$/,
            tinyCounter = 0,
            math = Math,
            mathRound = math.round,
            mathMin = math.min,
            mathMax = math.max,
            mathRandom = math.random;

        tinycolor = function (color, opts) {

            // If input is already a tinycolor, return itself
            if (typeof color == "object" && color.hasOwnProperty("_tc_id")) {
                return color;
            }

            var rgb = inputToRGB(color);
            var r = rgb.r, g = rgb.g, b = rgb.b, a = parseFloat(rgb.a), format = rgb.format;

            return {
                ok: rgb.ok,
                format: format,
                _tc_id: tinyCounter++,
                alpha: a,
                toHsv: function () {
                    var hsv = rgbToHsv(r, g, b);
                    return {h: hsv.h, s: hsv.s, v: hsv.v, a: a};
                },
                toHsvString: function () {
                    var hsv = rgbToHsv(r, g, b);
                    var h = mathRound(hsv.h * 360), s = mathRound(hsv.s * 100), v = mathRound(hsv.v * 100);
                    return (a == 1) ?
                        "hsv(" + h + ", " + s + "%, " + v + "%)" :
                        "hsva(" + h + ", " + s + "%, " + v + "%, " + a + ")";
                },
                toHsl: function () {
                    var hsl = rgbToHsl(r, g, b);
                    return {h: hsl.h, s: hsl.s, l: hsl.l, a: a};
                },
                toHslString: function () {
                    var hsl = rgbToHsl(r, g, b);
                    var h = mathRound(hsl.h * 360), s = mathRound(hsl.s * 100), l = mathRound(hsl.l * 100);
                    return (a == 1) ?
                        "hsl(" + h + ", " + s + "%, " + l + "%)" :
                        "hsla(" + h + ", " + s + "%, " + l + "%, " + a + ")";
                },
                toHex: function () {
                    return rgbToHex(r, g, b);
                },
                toHexString: function (force6Char) {
                    return rgbToHex(r, g, b, force6Char);
                },
                toHexString8: function () {
                    return rgbToHex(r, g, b, true) + pad2(mathRound(a * 255).toString(16));
                },
                toRgb: function () {
                    return {r: mathRound(r), g: mathRound(g), b: mathRound(b), a: a};
                },
                toRgbString: function () {
                    return (a == 1) ?
                        "rgb(" + mathRound(r) + ", " + mathRound(g) + ", " + mathRound(b) + ")" :
                        "rgba(" + mathRound(r) + ", " + mathRound(g) + ", " + mathRound(b) + ", " + a + ")";
                },
                toName: function () {
                    return hexNames[rgbToHex(r, g, b)] || false;
                },
                toFilter: function (opts, secondColor) {

                    var hex = rgbToHex(r, g, b, true);
                    var secondHex = hex;
                    var alphaHex = Math.round(parseFloat(a) * 255).toString(16);
                    var secondAlphaHex = alphaHex;
                    var gradientType = opts && opts.gradientType ? "GradientType = 1, " : "";

                    if (secondColor) {
                        var s = tinycolor(secondColor);
                        secondHex = s.toHex();
                        secondAlphaHex = Math.round(parseFloat(s.alpha) * 255).toString(16);
                    }

                    return "progid:DXImageTransform.Microsoft.gradient(" + gradientType + "startColorstr=#" + pad2(alphaHex) + hex + ",endColorstr=#" + pad2(secondAlphaHex) + secondHex + ")";
                },
                toString: function (format) {
                    format = format || this.format;
                    var formattedString = false;
                    if (format === "rgb") {
                        formattedString = this.toRgbString();
                    }
                    if (format === "hex") {
                        formattedString = this.toHexString();
                    }
                    if (format === "hex6") {
                        formattedString = this.toHexString(true);
                    }
                    if (format === "hex8") {
                        formattedString = this.toHexString8();
                    }
                    if (format === "name") {
                        formattedString = this.toName();
                    }
                    if (format === "hsl") {
                        formattedString = this.toHslString();
                    }
                    if (format === "hsv") {
                        formattedString = this.toHsvString();
                    }

                    return formattedString || this.toHexString(true);
                }
            };
        };

        // If input is an object, force 1 into "1.0" to handle ratios properly
        // String input requires "1.0" as input, so 1 will be treated as 1
        tinycolor.fromRatio = function (color) {

            if (typeof color == "object") {
                for (var i in color) {
                    if (color[i] === 1) {
                        color[i] = "1.0";
                    }
                }
            }

            return tinycolor(color);

        };

        // Given a string or object, convert that input to RGB
        // Possible string inputs:
        //
        //     "red"
        //     "#f00" or "f00"
        //     "#ff0000" or "ff0000"
        //     "rgb 255 0 0" or "rgb (255, 0, 0)"
        //     "rgb 1.0 0 0" or "rgb (1, 0, 0)"
        //     "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
        //     "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
        //     "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
        //     "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
        //     "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
        //
        function inputToRGB(color) {

            var rgb = {r: 0, g: 0, b: 0};
            var a = 1;
            var ok = false;
            var format = false;

            if (typeof color == "string") {
                color = stringInputToObject(color);
            }

            if (typeof color == "object") {
                if (color.hasOwnProperty("r") && color.hasOwnProperty("g") && color.hasOwnProperty("b")) {
                    rgb = rgbToRgb(color.r, color.g, color.b);
                    ok = true;
                    format = "rgb";
                }
                else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("v")) {
                    rgb = hsvToRgb(color.h, color.s, color.v);
                    ok = true;
                    format = "hsv";
                }
                else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("l")) {
                    rgb = hslToRgb(color.h, color.s, color.l);
                    ok = true;
                    format = "hsl";
                }

                if (color.hasOwnProperty("a")) {
                    a = color.a;
                }
            }

            rgb.r = mathMin(255, mathMax(rgb.r, 0));
            rgb.g = mathMin(255, mathMax(rgb.g, 0));
            rgb.b = mathMin(255, mathMax(rgb.b, 0));


            // Don't let the range of [0,255] come back in [0,1].
            // Potentially lose a little bit of precision here, but will fix issues where
            // .5 gets interpreted as half of the total, instead of half of 1.
            // If it was supposed to be 128, this was already taken care of in the conversion function
            if (rgb.r < 1) {
                rgb.r = mathRound(rgb.r);
            }
            if (rgb.g < 1) {
                rgb.g = mathRound(rgb.g);
            }
            if (rgb.b < 1) {
                rgb.b = mathRound(rgb.b);
            }

            return {
                ok: ok,
                format: (color && color.format) || format,
                r: rgb.r,
                g: rgb.g,
                b: rgb.b,
                a: a
            };
        }


        // Conversion Functions
        // --------------------

        // `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from:
        // <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript>

        // `rgbToRgb`
        // Handle bounds / percentage checking to conform to CSS color spec
        // <http://www.w3.org/TR/css3-color/>
        // *Assumes:* r, g, b in [0, 255] or [0, 1]
        // *Returns:* { r, g, b } in [0, 255]
        function rgbToRgb(r, g, b) {
            return {
                r: bound01(r, 255) * 255,
                g: bound01(g, 255) * 255,
                b: bound01(b, 255) * 255
            };
        }

        // `rgbToHsl`
        // Converts an RGB color value to HSL.
        // *Assumes:* r, g, and b are contained in [0, 255] or [0, 1]
        // *Returns:* { h, s, l } in [0,1]
        function rgbToHsl(r, g, b) {

            r = bound01(r, 255);
            g = bound01(g, 255);
            b = bound01(b, 255);

            var max = mathMax(r, g, b), min = mathMin(r, g, b);
            var h, s, l = (max + min) / 2;

            if (max == min) {
                h = s = 0; // achromatic
            }
            else {
                var d = max - min;
                s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
                switch (max) {
                    case r:
                        h = (g - b) / d + (g < b ? 6 : 0);
                        break;
                    case g:
                        h = (b - r) / d + 2;
                        break;
                    case b:
                        h = (r - g) / d + 4;
                        break;
                }

                h /= 6;
            }

            return {h: h, s: s, l: l};
        }

        // `hslToRgb`
        // Converts an HSL color value to RGB.
        // *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100]
        // *Returns:* { r, g, b } in the set [0, 255]
        function hslToRgb(h, s, l) {
            var r, g, b;

            h = bound01(h, 360);
            s = bound01(s, 100);
            l = bound01(l, 100);

            function hue2rgb(p, q, t) {
                if (t < 0) t += 1;
                if (t > 1) t -= 1;
                if (t < 1 / 6) return p + (q - p) * 6 * t;
                if (t < 1 / 2) return q;
                if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
                return p;
            }

            if (s === 0) {
                r = g = b = l; // achromatic
            }
            else {
                var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
                var p = 2 * l - q;
                r = hue2rgb(p, q, h + 1 / 3);
                g = hue2rgb(p, q, h);
                b = hue2rgb(p, q, h - 1 / 3);
            }

            return {r: r * 255, g: g * 255, b: b * 255};
        }

        // `rgbToHsv`
        // Converts an RGB color value to HSV
        // *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
        // *Returns:* { h, s, v } in [0,1]
        function rgbToHsv(r, g, b) {

            r = bound01(r, 255);
            g = bound01(g, 255);
            b = bound01(b, 255);

            var max = mathMax(r, g, b), min = mathMin(r, g, b);
            var h, s, v = max;

            var d = max - min;
            s = max === 0 ? 0 : d / max;

            if (max == min) {
                h = 0; // achromatic
            }
            else {
                switch (max) {
                    case r:
                        h = (g - b) / d + (g < b ? 6 : 0);
                        break;
                    case g:
                        h = (b - r) / d + 2;
                        break;
                    case b:
                        h = (r - g) / d + 4;
                        break;
                }
                h /= 6;
            }
            return {h: h, s: s, v: v};
        }

        // `hsvToRgb`
        // Converts an HSV color value to RGB.
        // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
        // *Returns:* { r, g, b } in the set [0, 255]
        function hsvToRgb(h, s, v) {
            h = bound01(h, 360) * 6;
            s = bound01(s, 100);
            v = bound01(v, 100);

            var i = math.floor(h),
                f = h - i,
                p = v * (1 - s),
                q = v * (1 - f * s),
                t = v * (1 - (1 - f) * s),
                mod = i % 6,
                r = [v, q, p, p, t, v][mod],
                g = [t, v, v, q, p, p][mod],
                b = [p, p, t, v, v, q][mod];

            return {r: r * 255, g: g * 255, b: b * 255};
        }

        // `rgbToHex`
        // Converts an RGB color to hex
        // Assumes r, g, and b are contained in the set [0, 255]
        // Returns a 3 or 6 character hex
        function rgbToHex(r, g, b, force6Char) {

            var hex = [
                pad2(mathRound(r).toString(16)),
                pad2(mathRound(g).toString(16)),
                pad2(mathRound(b).toString(16))
            ];

            // Return a 3 character hex if possible
            if (!force6Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) {
                return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
            }

            return hex.join("");
        }

        // `equals`
        // Can be called with any tinycolor input
        tinycolor.equals = function (color1, color2) {
            if (!color1 || !color2) {
                return false;
            }
            return tinycolor(color1).toRgbString() == tinycolor(color2).toRgbString();
        };
        tinycolor.random = function () {
            return tinycolor.fromRatio({
                r: mathRandom(),
                g: mathRandom(),
                b: mathRandom()
            });
        };


        // Modification Functions
        // ----------------------
        // Thanks to less.js for some of the basics here
        // <https://github.com/cloudhead/less.js/blob/master/lib/less/functions.js>


        tinycolor.desaturate = function (color, amount) {
            var hsl = tinycolor(color).toHsl();
            hsl.s -= ((amount || 10) / 100);
            hsl.s = clamp01(hsl.s);
            return tinycolor(hsl);
        };
        tinycolor.saturate = function (color, amount) {
            var hsl = tinycolor(color).toHsl();
            hsl.s += ((amount || 10) / 100);
            hsl.s = clamp01(hsl.s);
            return tinycolor(hsl);
        };
        tinycolor.greyscale = function (color) {
            return tinycolor.desaturate(color, 100);
        };
        tinycolor.lighten = function (color, amount) {
            var hsl = tinycolor(color).toHsl();
            hsl.l += ((amount || 10) / 100);
            hsl.l = clamp01(hsl.l);
            return tinycolor(hsl);
        };
        tinycolor.darken = function (color, amount) {
            var hsl = tinycolor(color).toHsl();
            hsl.l -= ((amount || 10) / 100);
            hsl.l = clamp01(hsl.l);
            return tinycolor(hsl);
        };
        tinycolor.complement = function (color) {
            var hsl = tinycolor(color).toHsl();
            hsl.h = (hsl.h + 0.5) % 1;
            return tinycolor(hsl);
        };


        // Combination Functions
        // ---------------------
        // Thanks to jQuery xColor for some of the ideas behind these
        // <https://github.com/infusion/jQuery-xcolor/blob/master/jquery.xcolor.js>

        tinycolor.triad = function (color) {
            var hsl = tinycolor(color).toHsl();
            var h = hsl.h * 360;
            return [
                tinycolor(color),
                tinycolor({h: (h + 120) % 360, s: hsl.s, l: hsl.l}),
                tinycolor({h: (h + 240) % 360, s: hsl.s, l: hsl.l})
            ];
        };
        tinycolor.tetrad = function (color) {
            var hsl = tinycolor(color).toHsl();
            var h = hsl.h * 360;
            return [
                tinycolor(color),
                tinycolor({h: (h + 90) % 360, s: hsl.s, l: hsl.l}),
                tinycolor({h: (h + 180) % 360, s: hsl.s, l: hsl.l}),
                tinycolor({h: (h + 270) % 360, s: hsl.s, l: hsl.l})
            ];
        };
        tinycolor.splitcomplement = function (color) {
            var hsl = tinycolor(color).toHsl();
            var h = hsl.h * 360;
            return [
                tinycolor(color),
                tinycolor({h: (h + 72) % 360, s: hsl.s, l: hsl.l}),
                tinycolor({h: (h + 216) % 360, s: hsl.s, l: hsl.l})
            ];
        };
        tinycolor.analogous = function (color, results, slices) {
            results = results || 6;
            slices = slices || 30;

            var hsl = tinycolor(color).toHsl();
            var part = 360 / slices;
            var ret = [tinycolor(color)];

            hsl.h *= 360;

            for (hsl.h = ((hsl.h - (part * results >> 1)) + 720) % 360; --results;) {
                hsl.h = (hsl.h + part) % 360;
                ret.push(tinycolor(hsl));
            }
            return ret;
        };
        tinycolor.monochromatic = function (color, results) {
            results = results || 6;
            var hsv = tinycolor(color).toHsv();
            var h = hsv.h, s = hsv.s, v = hsv.v;
            var ret = [];
            var modification = 1 / results;

            while (results--) {
                ret.push(tinycolor({h: h, s: s, v: v}));
                v = (v + modification) % 1;
            }

            return ret;
        };
        tinycolor.readable = function (color1, color2) {
            var a = tinycolor(color1).toRgb(), b = tinycolor(color2).toRgb();
            return (
                (b.r - a.r) * (b.r - a.r) +
                (b.g - a.g) * (b.g - a.g) +
                (b.b - a.b) * (b.b - a.b)
            ) > 0x28A4;
        };

        // Big List of Colors
        // ---------
        // <http://www.w3.org/TR/css3-color/#svg-color>
        var names = tinycolor.names = {
            aliceblue: "f0f8ff",
            antiquewhite: "faebd7",
            aqua: "0ff",
            aquamarine: "7fffd4",
            azure: "f0ffff",
            beige: "f5f5dc",
            bisque: "ffe4c4",
            black: "000",
            blanchedalmond: "ffebcd",
            blue: "00f",
            blueviolet: "8a2be2",
            brown: "a52a2a",
            burlywood: "deb887",
            burntsienna: "ea7e5d",
            cadetblue: "5f9ea0",
            chartreuse: "7fff00",
            chocolate: "d2691e",
            coral: "ff7f50",
            cornflowerblue: "6495ed",
            cornsilk: "fff8dc",
            crimson: "dc143c",
            cyan: "0ff",
            darkblue: "00008b",
            darkcyan: "008b8b",
            darkgoldenrod: "b8860b",
            darkgray: "a9a9a9",
            darkgreen: "006400",
            darkgrey: "a9a9a9",
            darkkhaki: "bdb76b",
            darkmagenta: "8b008b",
            darkolivegreen: "556b2f",
            darkorange: "ff8c00",
            darkorchid: "9932cc",
            darkred: "8b0000",
            darksalmon: "e9967a",
            darkseagreen: "8fbc8f",
            darkslateblue: "483d8b",
            darkslategray: "2f4f4f",
            darkslategrey: "2f4f4f",
            darkturquoise: "00ced1",
            darkviolet: "9400d3",
            deeppink: "ff1493",
            deepskyblue: "00bfff",
            dimgray: "696969",
            dimgrey: "696969",
            dodgerblue: "1e90ff",
            firebrick: "b22222",
            floralwhite: "fffaf0",
            forestgreen: "228b22",
            fuchsia: "f0f",
            gainsboro: "dcdcdc",
            ghostwhite: "f8f8ff",
            gold: "ffd700",
            goldenrod: "daa520",
            gray: "808080",
            green: "008000",
            greenyellow: "adff2f",
            grey: "808080",
            honeydew: "f0fff0",
            hotpink: "ff69b4",
            indianred: "cd5c5c",
            indigo: "4b0082",
            ivory: "fffff0",
            khaki: "f0e68c",
            lavender: "e6e6fa",
            lavenderblush: "fff0f5",
            lawngreen: "7cfc00",
            lemonchiffon: "fffacd",
            lightblue: "add8e6",
            lightcoral: "f08080",
            lightcyan: "e0ffff",
            lightgoldenrodyellow: "fafad2",
            lightgray: "d3d3d3",
            lightgreen: "90ee90",
            lightgrey: "d3d3d3",
            lightpink: "ffb6c1",
            lightsalmon: "ffa07a",
            lightseagreen: "20b2aa",
            lightskyblue: "87cefa",
            lightslategray: "789",
            lightslategrey: "789",
            lightsteelblue: "b0c4de",
            lightyellow: "ffffe0",
            lime: "0f0",
            limegreen: "32cd32",
            linen: "faf0e6",
            magenta: "f0f",
            maroon: "800000",
            mediumaquamarine: "66cdaa",
            mediumblue: "0000cd",
            mediumorchid: "ba55d3",
            mediumpurple: "9370db",
            mediumseagreen: "3cb371",
            mediumslateblue: "7b68ee",
            mediumspringgreen: "00fa9a",
            mediumturquoise: "48d1cc",
            mediumvioletred: "c71585",
            midnightblue: "191970",
            mintcream: "f5fffa",
            mistyrose: "ffe4e1",
            moccasin: "ffe4b5",
            navajowhite: "ffdead",
            navy: "000080",
            oldlace: "fdf5e6",
            olive: "808000",
            olivedrab: "6b8e23",
            orange: "ffa500",
            orangered: "ff4500",
            orchid: "da70d6",
            palegoldenrod: "eee8aa",
            palegreen: "98fb98",
            paleturquoise: "afeeee",
            palevioletred: "db7093",
            papayawhip: "ffefd5",
            peachpuff: "ffdab9",
            peru: "cd853f",
            pink: "ffc0cb",
            plum: "dda0dd",
            powderblue: "b0e0e6",
            purple: "800080",
            red: "f00",
            rosybrown: "bc8f8f",
            royalblue: "4169e1",
            saddlebrown: "8b4513",
            salmon: "fa8072",
            sandybrown: "f4a460",
            seagreen: "2e8b57",
            seashell: "fff5ee",
            sienna: "a0522d",
            silver: "c0c0c0",
            skyblue: "87ceeb",
            slateblue: "6a5acd",
            slategray: "708090",
            slategrey: "708090",
            snow: "fffafa",
            springgreen: "00ff7f",
            steelblue: "4682b4",
            tan: "d2b48c",
            teal: "008080",
            thistle: "d8bfd8",
            tomato: "ff6347",
            turquoise: "40e0d0",
            violet: "ee82ee",
            wheat: "f5deb3",
            white: "fff",
            whitesmoke: "f5f5f5",
            yellow: "ff0",
            yellowgreen: "9acd32"
        };

        // Make it easy to access colors via `hexNames[hex]`
        var hexNames = tinycolor.hexNames = flip(names);


        // Utilities
        // ---------

        // `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }`
        function flip(o) {
            var flipped = {};
            for (var i in o) {
                if (o.hasOwnProperty(i)) {
                    flipped[o[i]] = i;
                }
            }
            return flipped;
        }

        // Take input from [0, n] and return it as [0, 1]
        function bound01(n, max) {
            if (isOnePointZero(n)) {
                n = "100%";
            }

            var processPercent = isPercentage(n);
            n = mathMin(max, mathMax(0, parseFloat(n)));

            // Automatically convert percentage into number
            if (processPercent) {
                n = n * (max / 100);
            }

            // Handle floating point rounding errors
            if (math.abs(n - max) < 0.000001) {
                return 1;
            }
            else if (n >= 1) {
                return (n % max) / parseFloat(max);
            }
            return n;
        }

        // Force a number between 0 and 1
        function clamp01(val) {
            return mathMin(1, mathMax(0, val));
        }

        // Parse an integer into hex
        function parseHex(val) {
            return parseInt(val, 16);
        }

        // Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
        // <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
        function isOnePointZero(n) {
            return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1;
        }

        // Check to see if string passed in is a percentage
        function isPercentage(n) {
            return typeof n === "string" && n.indexOf('%') != -1;
        }

        // Force a hex value to have 2 characters
        function pad2(c) {
            return c.length == 1 ? '0' + c : '' + c;
        }

        var matchers = (function () {

            // <http://www.w3.org/TR/css3-values/#integers>
            var CSS_INTEGER = "[-\\+]?\\d+%?";

            // <http://www.w3.org/TR/css3-values/#number-value>
            var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?";

            // Allow positive/negative integer/number.  Don't capture the either/or, just the entire outcome.
            var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")";

            // Actual matching.
            // Parentheses and commas are optional, but not required.
            // Whitespace can take the place of commas or opening paren
            var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
            var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";

            return {
                rgb: new RegExp("rgb" + PERMISSIVE_MATCH3),
                rgba: new RegExp("rgba" + PERMISSIVE_MATCH4),
                hsl: new RegExp("hsl" + PERMISSIVE_MATCH3),
                hsla: new RegExp("hsla" + PERMISSIVE_MATCH4),
                hsv: new RegExp("hsv" + PERMISSIVE_MATCH3),
                hex3: /^([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
                hex6: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
                hex8: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/
            };
        })();

        // `stringInputToObject`
        // Permissive string parsing.  Take in a number of formats, and output an object
        // based on detected format.  Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}`
        function stringInputToObject(color) {

            color = color.replace(trimLeft, '').replace(trimRight, '').toLowerCase();
            var named = false;
            if (names[color]) {
                color = names[color];
                named = true;
            }
            else if (color == 'transparent') {
                return {r: 0, g: 0, b: 0, a: 0};
            }

            // Try to match string input using regular expressions.
            // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360]
            // Just return an object and let the conversion functions handle that.
            // This way the result will be the same whether the tinycolor is initialized with string or object.
            var match;
            if ((match = matchers.rgb.exec(color))) {
                return {r: match[1], g: match[2], b: match[3]};
            }
            if ((match = matchers.rgba.exec(color))) {
                return {r: match[1], g: match[2], b: match[3], a: match[4]};
            }
            if ((match = matchers.hsl.exec(color))) {
                return {h: match[1], s: match[2], l: match[3]};
            }
            if ((match = matchers.hsla.exec(color))) {
                return {h: match[1], s: match[2], l: match[3], a: match[4]};
            }
            if ((match = matchers.hsv.exec(color))) {
                return {h: match[1], s: match[2], v: match[3]};
            }
            if ((match = matchers.hex6.exec(color))) {
                return {
                    r: parseHex(match[1]),
                    g: parseHex(match[2]),
                    b: parseHex(match[3]),
                    format: named ? "name" : "hex"
                };
            }
            if ((match = matchers.hex8.exec(color))) {
                return {
                    r: parseHex(match[1]),
                    g: parseHex(match[2]),
                    b: parseHex(match[3]),
                    a: parseHex(match[4]) / 255,
                    format: named ? "name" : "hex"
                };
            }
            if ((match = matchers.hex3.exec(color))) {
                return {
                    r: parseHex(match[1] + '' + match[1]),
                    g: parseHex(match[2] + '' + match[2]),
                    b: parseHex(match[3] + '' + match[3]),
                    format: named ? "name" : "hex"
                };
            }

            return false;
        }

        // Everything is ready, expose to window
        //tinycolor;

    })();

    return $.fn.n2spectrum;
});

N2D('ExpertMode', function ($, undefined) {

    /**
     * @param allowed
     * @constructor
     */
    function ExpertMode(allowed) {
        this.app = 'system';
        this.key = 'IsExpert';
        this.isExpert = 0;

        this.style = $('<div style="display: none;"></div>').appendTo('body');

        if (!allowed) {
            this.switches = $();
            this.disable(false);
        } else {

            this.switches = $('.n2-expert-switch')
                .on({
                    mousedown: $.proxy(N2Classes.WindowManager.setMouseDownArea, null, 'expertClicked'),
                    click: $.proxy(this.switchExpert, this, true)
                });

            this.load();
            if (!this.isExpert) {
                this.disable(false);
            }

            $.jStorage.listenKeyChange(this.app + this.key, $.proxy(this.load, this));
        }
    }

    ExpertMode.prototype.load = function () {
        var isExpert = parseInt($.jStorage.get(this.app + this.key, 0));
        if (isExpert != this.isExpert) {
            this.switchExpert(false, false);
        }
    };

    ExpertMode.prototype.set = function (value, needSet) {
        this.isExpert = value;
        if (needSet) {
            $.jStorage.set(this.app + this.key, value);
        }
    };

    ExpertMode.prototype.switchExpert = function (needSet, e) {
        if (e) {
            e.preventDefault();
        }
        if (!this.isExpert) {
            this.enable(needSet);
        } else {
            this.disable(needSet);
        }
    };

    ExpertMode.prototype.measureElement = function () {
        var el = null,
            scrollTop = $(window).scrollTop(),
            cutoff = scrollTop + 62,
            cutoffBottom = scrollTop + $(window).height() - 100;
        $('.n2-content-area > .n2-heading-bar,.n2-content-area > .n2-form-tab ,#n2-admin .n2-content-area form > .n2-form > .n2-form-tab').each(function () {
            var $el = $(this);
            if ($el.offset().top > cutoff) {
                if (!$el.hasClass('n2-heading-bar')) {
                    el = $el;
                }
                return false;
            } else if ($el.offset().top + $el.height() > cutoffBottom) {
                if (!$el.hasClass('n2-heading-bar')) {
                    el = $el;
                }
                return false;
            }
        });
        this.measuredElement = el;
    };

    ExpertMode.prototype.scrollToMeasured = function () {

        if (this.measuredElement !== null) {
            while (this.measuredElement.length && !this.measuredElement.is(':VISIBLE')) {
                this.measuredElement = this.measuredElement.prev();
            }
            if (this.measuredElement.length != 0) {
                $('html,body').scrollTop(this.measuredElement.offset().top - 102);
            }
        }
    };

    ExpertMode.prototype.enable = function (needSet) {
        this.measureElement();
        this.changeStyle('');
        this.set(1, needSet);
        this.switches.addClass('n2-active');
        $('html').addClass('n2-in-expert');

        if (needSet) {
            this.scrollToMeasured();
        }
    };

    ExpertMode.prototype.disable = function (needSet) {
        this.measureElement();
        this.changeStyle('.n2-expert{display: none !important;}');
        this.set(0, needSet);
        this.switches.removeClass('n2-active');
        $('html').removeClass('n2-in-expert');

        if (needSet) {
            this.scrollToMeasured();
        }
    };

    ExpertMode.prototype.changeStyle = function (style) {
        this.style.html('<style type="text/css">' + style + '</style>');
    };

    return function (app, allowed) {
        return new ExpertMode(app, allowed);
    }
});
N2D('Form', function ($, undefined) {

    $(window).ready(function () {
        $('input[data-disabled]').on('focus', function () {
            this.blur();
        });
    });

    var registeredBeforeUnload = false;

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param url
     * @param values
     * @constructor
     */
    function Form(id, url, values) {
        this.form = $('#' + id)
            .on('saved', $.proxy(this.updateSerializedData, this))
            .data('form', this);

        this.updateSerializedData();

        this.url = url;

        this.values = values;

        // Special fix for Joomla 1.6, 1.7 & 2.5. Speedy save!
        if (typeof document.formvalidator !== "undefined") {
            document.formvalidator.isValid = function () {
                return true;
            };
        }

        $(window).on('n2-before-unload', $.proxy(this.onBeforeUnload, this));
        this.registerBeforeUnload();

        $('input, textarea').on('keyup', function (e) {
            if (e.which === 27) {
                e.target.blur();
                e.stopPropagation();
            }
        });
    }

    Form.prototype.registerBeforeUnload = function () {
        if (!registeredBeforeUnload) {
            $(window).on('beforeunload', function (e) {
                if (nextend.askToSave && registeredBeforeUnload + 180000 < $.now()) {
                    var data = {
                        changed: false
                    };
                    $(window).triggerHandler('n2-before-unload', data);

                    if (data.changed) {
                        var confirmationMessage = n2_('The changes you made will be lost if you navigate away from this page.');

                        (e || window.event).returnValue = confirmationMessage;
                        return confirmationMessage;
                    }
                }
            });
            registeredBeforeUnload = $.now();
        }
    };

    Form.prototype.onBeforeUnload = function (e, data) {
        if (!data.changed && this.isChanged()) {
            data.changed = true;
        }
    };

    Form.prototype.isChanged = function () {
        this.form.triggerHandler('checkChanged');

        return this.serialized != this.form.serialize();
    };

    Form.prototype.updateSerializedData = function () {
        this.serialized = this.form.serialize();
    };

    Form.submit = function (query) {
        nextend.askToSave = false;
        setTimeout(function () {
            $(query).submit();
        }, 300);
        return false;
    };

    return Form;
});
N2D('FormElement', function ($, undefined) {

    /**
     * @memberOf N2Classes
     * 
     * @constructor
     */
    function FormElement() {
        this.connectedField = null;
        this.element.data('field', this);
    }

    FormElement.prototype.triggerOutsideChange = function () {
        this.element.triggerHandler('outsideChange', this);
        this.element.triggerHandler('nextendChange', this);
    };

    FormElement.prototype.triggerInsideChange = function () {
        this.element.triggerHandler('insideChange', this);
        this.element.triggerHandler('nextendChange', this);
    };

    FormElement.prototype.focus = function (shouldOpen) {
        if (this.connectedField) {
            this.connectedField.focus(shouldOpen);
        }
    };

    return FormElement;
});

N2D('FormElementText', ['FormElement'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @constructor
     */
    function FormElementText(id) {
        this.element = $('#' + id).on({
            focus: $.proxy(this._focus, this),
            blur: $.proxy(this._blur, this),
            change: $.proxy(this.change, this)
        });

        this.tagName = this.element.prop('tagName');

        this.parent = this.element.parent();

        N2Classes.FormElement.prototype.constructor.apply(this, arguments);
    }


    FormElementText.prototype = Object.create(N2Classes.FormElement.prototype);
    FormElementText.prototype.constructor = FormElementText;


    FormElementText.prototype._focus = function () {
        this.parent.addClass('focus');

        if (this.tagName != 'TEXTAREA') {
            this.element.on('keypress.n2-text', $.proxy(function (e) {
                if (e.which == 13) {
                    this.element.off('keypress.n2-text');
                    this.element.trigger('blur');
                }
            }, this));
        }
    };

    FormElementText.prototype._blur = function () {
        this.parent.removeClass('focus');
    };

    FormElementText.prototype.change = function () {

        this.triggerOutsideChange();
    };

    FormElementText.prototype.insideChange = function (value) {
        this.element.val(value);

        this.triggerInsideChange();
    };

    FormElementText.prototype.focus = function (shouldOpen) {
        if (this.connectedField) {
            this.connectedField.focus(shouldOpen);
        } else if (shouldOpen) {
            this.element.focus().select();
        }
    };

    return FormElementText;
});
N2D('Notification', function ($, undefined) {

    /**
     * @alias N2Classes.notification
     * @constructor
     */
    function Notification() {
        /**
         * @type {NotificationStack[]}
         */
        this.stack = [];
        this.tween = null;

        N2R('documentReady', $.proxy(function ($) {
            var mainTopBar = $('#n2-admin').find('.n2-main-top-bar');
            if (mainTopBar.length > 0) {
                var stack = new N2Classes.NotificationStack($('#n2-admin').find('.n2-main-top-bar'));
                stack.enableStack();
            } else {
                var stack = new N2Classes.NotificationStackModal($('#n2-admin'));
                stack.enableStack();
            }
        }, this));
    }


    Notification.prototype.add = function (stack) {
        this.stack.push(stack);
    };

    Notification.prototype.popStack = function () {
        this.stack.pop();
    };

    /**
     *
     * @returns {NotificationStack}
     */
    Notification.prototype.getCurrentStack = function () {
        return this.stack[this.stack.length - 1];
    };

    Notification.prototype.success = function (message, parameters) {
        this.getCurrentStack().success(message, parameters);
    };

    Notification.prototype.error = function (message, parameters) {
        this.getCurrentStack().error(message, parameters);
    };

    Notification.prototype.notice = function (message, parameters) {
        this.getCurrentStack().notice(message, parameters);
    };

    return new Notification();
});
N2D('NotificationStack', function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @constructor
     */
    function NotificationStack(bar) {
        this.messages = [];
        this.isShow = false;
        this.importantOnly = 0;

        this.importantOnlyNode = $('<div class="n2-notification-important n2-h5 ' + (this.importantOnly ? 'n2-active' : '') + '"><span>' + n2_('Show only errors') + '</span><div class="n2-checkbox n2-light"><i class="n2-i n2-i-tick"></i></div></div>')
            .on('click', $.proxy(this.changeImportant, this));
        $.jStorage.listenKeyChange('ss-important-only', $.proxy(this.importantOnlyChanged, this));
        this.importantOnlyChanged();

        this._init(bar);
        this.emptyMessage = $('<div class="n2-notification-empty n2-h4">' + n2_('There are no messages to display.') + '</div>');
    }

    NotificationStack.prototype._init = function (bar) {

        this.showButton = bar.find('.n2-notification-button')
            .on('click', $.proxy(this.hideOrShow, this));

        var settings = $('<div class="n2-notification-settings"></div>')
            .append($('<div class="n2-button n2-button-normal n2-button-s n2-button-blue n2-radius-s n2-h5 n2-uc n2-notification-clear">' + n2_('Got it!') + '</div>').on('click', $.proxy(this.clear, this)))
            .append(this.importantOnlyNode);


        this.container = this.messageContainer = $('<div class="n2-notification-center n2-border-radius-br n2-border-radius-bl"></div>')
            .append(settings)
            .appendTo(bar);
    };

    NotificationStack.prototype.enableStack = function () {
        N2Classes.Notification.add(this);
    };

    NotificationStack.prototype.popStack = function () {
        N2Classes.Notification.popStack();
    };

    NotificationStack.prototype.hideOrShow = function (e) {
        e.preventDefault();
        if (this.isShow) {
            this.hide()
        } else {
            this.show();
        }
    };

    NotificationStack.prototype.show = function () {
        if (!this.isShow) {
            this.isShow = true;

            if (this.messages.length == 0) {
                this.showEmptyMessage();
            }

            if (this.showButton) {
                this.showButton.addClass('n2-active');
            }
            this.container.addClass('n2-active');

            this.container.css('display', 'block');

            this._animateShow();
        }
    };

    NotificationStack.prototype.hide = function () {
        if (this.isShow) {
            if (this.showButton) {
                this.showButton.removeClass('n2-active');
            }
            this.container.removeClass('n2-active');

            this._animateHide();

            this.container.css('display', 'none');

            this.isShow = false;
        }
    };

    NotificationStack.prototype._animateShow = function () {
        if (this.tween) {
            this.tween.pause();
        }
        this.tween = NextendTween.fromTo(this.container, 0.4, {
            opacity: 0
        }, {
            opacity: 1
        });
    };

    NotificationStack.prototype._animateHide = function () {
        if (this.tween) {
            this.tween.pause();
        }
    };

    NotificationStack.prototype.success = function (message, parameters) {
        this._message('success', n2_('success'), message, parameters);
    };

    NotificationStack.prototype.error = function (message, parameters) {
        this._message('error', n2_('error'), message, parameters);
    };

    NotificationStack.prototype.notice = function (message, parameters) {
        this._message('notice', n2_('notice'), message, parameters);
    };

    NotificationStack.prototype._message = function (type, label, message, parameters) {

        this.hideEmptyMessage();

        parameters = $.extend({
            timeout: false,
            remove: false
        }, parameters);

        var messageNode = $('<div></div>');

        if (parameters.timeout) {
            setTimeout($.proxy(function () {
                this.hideMessage(messageNode, parameters.remove);
            }, this), parameters.timeout * 1000);
        }

        messageNode
            .addClass('n2-table n2-table-fixed n2-h3 n2-border-radius n2-notification-message n2-notification-message-' + type)
            .append($('<div class="n2-tr"></div>')
                .append('<div class="n2-td n2-first"><i class="n2-i n2-i-n-' + type + '"/></div>')
                .append('<div class="n2-td n2-message"><h4 class="n2-h4 n2-uc">' + label + '</h4><p class="n2-h4">' + message + '</p></div>'))
            .prependTo(this.messageContainer);

        this.messages.push(messageNode);
        if (this.messages.length > 3) {
            this.messages.shift().remove();
        }

        if (!this.importantOnly || type == 'error' || type == 'notice') {
            this.show();
        }
        return messageNode;
    };

    NotificationStack.prototype.hideMessage = function (message, remove) {
        if (remove) {
            this.deleteMessage(message);
        } else {
            this.hide();
        }
    };

    NotificationStack.prototype.deleteMessage = function (message) {
        var index = $.inArray(message, this.messages);
        if (index > -1) {
            this.messages.splice(index, 1);
            message.remove();
        }
        if (this.messages.length == 0) {
            this.hide();
        }
    };
    NotificationStack.prototype.clear = function () {
        for (var i = this.messages.length - 1; i >= 0; i--) {
            this.messages.pop().remove();
        }

        this.showEmptyMessage();

        this.hide();
    };
    NotificationStack.prototype.changeImportant = function () {
        if (this.importantOnly) {
            $.jStorage.set('ss-important-only', 0);
        } else {
            $.jStorage.set('ss-important-only', 1);
        }
    };

    NotificationStack.prototype.importantOnlyChanged = function () {
        this.importantOnly = parseInt($.jStorage.get('ss-important-only', 0));
        if (this.importantOnly) {
            this.importantOnlyNode.addClass('n2-active');
        } else {
            this.importantOnlyNode.removeClass('n2-active');
        }
    };

    NotificationStack.prototype.showEmptyMessage = function () {
        this.emptyMessage.prependTo(this.container);
    };

    NotificationStack.prototype.hideEmptyMessage = function () {
        this.emptyMessage.detach();
    };

    return NotificationStack;
});
N2D('NotificationStackModal', ['NotificationStack'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @constructor
     * @augments NotificationStack
     */
    function NotificationStackModal() {
        N2Classes.NotificationStack.prototype.constructor.apply(this, arguments);
    }

    NotificationStackModal.prototype = Object.create(N2Classes.NotificationStack.prototype);
    NotificationStackModal.prototype.constructor = NotificationStackModal;


    NotificationStackModal.prototype._init = function (bar) {
        var settings = $('<div class="n2-notification-settings"></div>')
            .append($('<div class="n2-button n2-button-normal n2-button-s n2-button-blue n2-radius-s n2-h5 n2-uc n2-notification-clear">'+n2_('Got it!')+'</div>').on('click', $.proxy(this.clear, this)))
            .append(this.importantOnlyNode);

        this.messageContainer = $('<div class="n2-notification-center n2-border-radius"></div>')
            .append(settings);
        this.container = $('<div class="n2-notification-center-modal"></div>')
            .append(this.messageContainer)
            .appendTo(bar);
    };

    NotificationStackModal.prototype.show = function () {
        if (document.activeElement) {
            document.activeElement.blur();
        }
        N2Classes.Esc.add($.proxy(function () {
            this.clear();
            return false;
        }, this));

        N2Classes.NotificationStack.prototype.show.apply(this, arguments);
    };

    NotificationStackModal.prototype.hide = function () {
        N2Classes.Esc.pop();

        N2Classes.NotificationStack.prototype.hide.apply(this, arguments);
    };

    NotificationStackModal.prototype._animateShow = function () {

    };

    NotificationStackModal.prototype._animateHide = function () {

    };

    return NotificationStackModal;
});
N2D('FormElementAutocompleteSimple', function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param values
     * @constructor
     */
    function FormElementAutocompleteSimple(id, values) {
        this.element = $('#' + id).data('autocomplete', this);
        this.element.nUIAutocomplete({
            positionTo: '.n2-form-element-autocomplete',
            y: -2,
            appendTo: $.proxy(function () {
                return this.element.closest('.n2-scrollable, body')
            }, this),
            source: function () {
                return values;
            },
            select: function (event, ui) {
                $(this).val(ui.value).trigger('change');
            }
        });
    }

    return FormElementAutocompleteSimple;
});
N2D('FormElementAutocomplete', function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param tags
     * @constructor
     */
    function FormElementAutocomplete(id, tags) {
        this.tags = tags;
        this.element = $('#' + id).data('autocomplete', this);

        this.element.nUIAutocomplete({
            positionTo: '.n2-form-element-autocomplete',
            y: -2,
            appendTo: $.proxy(function () {
                return this.element.closest('.n2-scrollable, body')
            }, this),
            source: $.proxy(function () {
                return this.tags;
            }, this),
            select: function (event, ui) {
                var terms = this.value.split(/,/);
                terms.pop();
                terms.push(ui.value);
                terms.push("");
                this.value = terms.join(",");
                $(this).trigger('change');
            }
        });

        this.element.siblings('.n2-form-element-clear')
            .on('click', $.proxy(this.clear, this));
    };

    FormElementAutocomplete.prototype.clear = function (e) {
        e.preventDefault();
        e.stopPropagation();
        this.element.val('').trigger('change');
    };

    FormElementAutocomplete.prototype.setTags = function (tags) {
        this.tags = tags;
    };

    return FormElementAutocomplete;

});
N2D('BasicCSSFont', ['BasicCSSSkeleton'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @constructor
     */
    function BasicCSSFont() {
        this._singular = 'font';
        this._prular = 'fonts';
        N2Classes.BasicCSSSkeleton.prototype.constructor.apply(this, arguments);

        this.form = {
            afont: $('#layerfamily'),
            color: $('#layercolor'),
            size: $('#layersize'),
            weight: $('#layerweight'),
            lineheight: $('#layerlineheight'),
            align: $('#layertextalign'),
            underline: $('#layerdecoration'),
            italic: $('#layerdecoration')

        };

        this.loaded();
    }

    BasicCSSFont.prototype = Object.create(N2Classes.BasicCSSSkeleton.prototype);
    BasicCSSFont.prototype.constructor = BasicCSSFont;

    BasicCSSFont.prototype.setValue = function (value) {
        for (var i = 0; i < value.length; i++) {
            if (value[i].bold !== undefined) {
                if (value[i].weight !== undefined) {
                    delete value[i].bold;
                } else {
                    if (value[i].bold == 1) {
                        value[i].weight = 700;
                    } else if (value[i].bold > 0) {
                        value[i].weight = value[i].bold;
                    }
                    delete value[i].bold;
                }
            }
        }
        this.value = value;
    }

    BasicCSSFont.prototype._transformsize = function (value) {
        return value.split('||').join('|*|');
    };

    BasicCSSFont.prototype._setsize = function (tab, value) {
        tab.size = value.replace('|*|', '||');
    };

    BasicCSSFont.prototype._transformweight = function (value) {
        return parseInt(value);
    };

    BasicCSSFont.prototype._setweight = function (tab, value) {
        tab.weight = parseInt(value);
    };

    BasicCSSFont.prototype._transformunderline = function (value) {
        return [
            this.value[this.activeTab].italic == 1 ? 'italic' : '',
            value == 1 ? 'underline' : ''
        ].join('||');
    };

    BasicCSSFont.prototype._setunderline = function (tab, value) {
        var values = value.split('||');
        tab.underline = (values[1] == 'underline' ? 1 : 0);
    };

    BasicCSSFont.prototype._transformitalic = function (value) {
        return [
            value == 1 ? 'italic' : '',
            this.value[this.activeTab].underline == 1 ? 'underline' : ''
        ].join('||');
    };

    BasicCSSFont.prototype._setitalic = function (tab, value) {
        var values = value.split('||');
        tab.italic = (values[0] == 'italic' ? 1 : 0);
    };

    return BasicCSSFont;
});
N2D('BasicCSSSkeleton', function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param manager
     * @constructor
     */
    function BasicCSSSkeleton(manager) {
        this.hasVisuals = false;
        this.isInsideChange = false;
        this.isReload = false;
        this.manager = manager;
        this.$container = manager.$container.find('#n2-tab-basiccss' + this._singular);
        this.$visuals = this.$container.find('.n2-css-name');
        this.$visualsLabel = this.$visuals.find('.n2-css-name-label');
        this.$visualsList = this.$visuals.find('.n2-css-name-list');
        this.$tabsContainer = this.$container.find('.n2-css-tab');
        this.$reset = this.$container.find('.n2-css-tab-reset').on('click', $.proxy(function (e) {
            this.value[this.activeTab] = {};
            this._lazySave(e);
            this.activateTab(this.activeTab);
        }, this));
        this.$more = this.$container.find('.n2-basiccss-more').on('click', $.proxy(function (e) {
            e.preventDefault();
            this.visuals[this.activeVisual].field.show(e);
        }, this));

        this.activeVisual = 0;
        this.activeTab = 0;
        this.tabs = [];
    }

    BasicCSSSkeleton.prototype.loaded = function () {
        for (var k in this.form) {
            this.form[k].on({
                nextendChange: $.proxy(this.changeValue, this, k)
            });
        }
    };

    BasicCSSSkeleton.prototype.changeValue = function (name, e) {
        if (!this.isReload) {
            if (typeof this['_set' + name] == 'function') {
                this['_set' + name](this.value[this.activeTab], this.form[name].val());
            } else {
                this.value[this.activeTab][name] = this.form[name].val();
            }

            this._lazySave(e);
        }
    };

    BasicCSSSkeleton.prototype._lazySave = NextendDeBounce(function (e) {
        this.isInsideChange = true;
        var value = this.getBase64();
        this.visuals[this.activeVisual].field.save(e, value);
        this.visuals[this.activeVisual].value = value;
        this.isInsideChange = false;
    }, 50);

    BasicCSSSkeleton.prototype.save = function (data) {
        this.isInsideChange = true;
        for (var k in data) {
            this.visualsByName[k].field.save({}, data[k]);
            this.visualsByName[k].value = data[k];
        }
        this.isInsideChange = false;
    };

    BasicCSSSkeleton.prototype.getBase64 = function () {
        return N2Classes.Base64.encode(JSON.stringify({
            name: n2_('Static'),
            data: this.value
        }));
    };

    BasicCSSSkeleton.prototype.load = function (values, visuals) {
        this.hasVisuals = visuals.length > 0;
        this.$container.toggleClass('n2-css-has-' + this._singular, this.hasVisuals);
        if (this.hasVisuals) {
            this.visuals = [];
            this.visualsByName = {};

            this.$visualsList.html('');

            this.$visuals.toggleClass('n2-multiple', visuals.length > 1);
            for (var i = 0; i < visuals.length; i++) {
                var visual = visuals[i];
                this.visualsByName[visual.name] = {
                    value: values[visual.name],
                    mode: visual.mode,
                    field: visual.field
                };

                visual.field.element
                    .off('.basiccss')
                    .on('outsideChange.basiccss', $.proxy(this.loadSingleValue, this, i, visual.name));
                this.visuals.push(this.visualsByName[visual.name]);

                $('<span>' + visual.field.getLabel() + '</span>').on('click', $.proxy(function (i, e) {
                    this.activateVisual(i);
                    this.activateTab(0);
                }, this, i)).appendTo(this.$visualsList);
            }

            this.activateVisual(0);

            this.activateTab(0);
        }
    };

    BasicCSSSkeleton.prototype.loadSingleValue = function (i, k, e) {
        if (!this.isInsideChange) {
            this.visuals[i].value = this.visuals[i].field.element.val();
            if (this.activeVisual == i) {
                this.activateVisual(i);
                this.activateTab(this.activeTab);
            }
        }
    };

    BasicCSSSkeleton.prototype.activateVisual = function (index) {
        this.activeVisual = index;

        this.$visualsLabel.html(this.visuals[index].field.getLabel());

        nextend[this._singular + 'Manager'].getDataFromController(this.visuals[index].value, {previewMode: this.visuals[index].mode}, $.proxy(function (value, tabs) {
            this.setValue(value);
            this.setTabs(tabs);
        }, this));
    };

    BasicCSSSkeleton.prototype.setValue = function (value) {
        this.value = value;
    }

    BasicCSSSkeleton.prototype.activateTab = function (index) {
        this.isReload = true;
        this.activeTab = index;
        this.$container.toggleClass('n2-css-show-reset', index != 0);
        var value = (index == 0 ? this.value[index] : $.extend({}, this.value[0], this.value[index]));
        for (var k in value) {
            if (typeof this.form[k] !== 'undefined') {
                if (typeof this['_transform' + k] == 'function') {
                    this.form[k].data('field').insideChange(this['_transform' + k](value[k]));
                } else {
                    this.form[k].data('field').insideChange(value[k]);
                }
            }
        }

        this.$tabs.removeClass('n2-active').eq(index).addClass('n2-active');
        this.isReload = false;
    };

    BasicCSSSkeleton.prototype.setTabs = function (tabs) {
        this.tabs = tabs;
        this.$tabsContainer.html('');

        for (var i = 0; i < tabs.length; i++) {
            $('<span>' + tabs[i] + '</span>').on('click', $.proxy(function (i, e) {
                this.activateTab(i);
            }, this, i)).appendTo(this.$tabsContainer);
        }
        this.$tabs = this.$tabsContainer.find('span');
    };

    BasicCSSSkeleton.prototype.serialize = function () {
        if (this.hasVisuals) {
            var serialized = {};
            for (var k in this.visualsByName) {
                serialized[k] = this.visualsByName[k].value;
            }
            return serialized;
        }
        return {};
    };

    BasicCSSSkeleton.prototype.unSerialize = function (serialized) {
        for (var k in serialized) {
            if (serialized.hasOwnProperty(k)) {
                this.visualsByName[k].field.save({}, serialized[k]);
                this.visualsByName[k].value = serialized[k];
            }
        }
    };

    return BasicCSSSkeleton;
});
N2D('BasicCSSStyle', ['BasicCSSSkeleton'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @constructor
     */
    function BasicCSSStyle() {
        this._singular = 'style';
        this._prular = 'styles';
        N2Classes.BasicCSSSkeleton.prototype.constructor.apply(this, arguments);


        this.form = {
            backgroundcolor: $('#layerbackgroundcolor'),
            opacity: $('#layeropacity'),
            padding: $('#layerpadding'),
            border: $('#layerborder'),
            borderradius: $('#layerborderradius')
        };

        this.loaded();
    }

    BasicCSSStyle.prototype = Object.create(N2Classes.BasicCSSSkeleton.prototype);
    BasicCSSStyle.prototype.constructor = BasicCSSStyle;

    return BasicCSSStyle;
});
N2D('BasicCSS', function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param ajaxUrl
     * @constructor
     */
    function BasicCSS(id, ajaxUrl) {
        this.underActivate = false;
        this.inPresetList = false;
        this.$container = $('#' + id);
        this.ajaxUrl = ajaxUrl;

        this.throttleSetTimeout = null;
        this.throttleExitTimeout = null;

        this.storage = {};

        this.assets = {
            font: new N2Classes.BasicCSSFont(this),
            style: new N2Classes.BasicCSSStyle(this)
        };


        this.$preset = $('<div id="n2-tab-basiccsspreset"><div class="n2-editor-header n2-h2 n2-uc"><span class="n2-css-name n2-css-name-label">' + n2_('Preset') + '</span></div></div>').prependTo(this.$container);


        $('<div class="n2-ss-editor-window-notice n2-ss-responsive-helper n2-h5">' + n2_(window.n2_printf('NOTE: Layer design changes apply to each device. Watch <a href="%s" target="_blank">video tutorial</a> to learn responsive tools.', "https://www.youtube.com/watch?v=yGpVsrzwt1U&index=4&list=PLSawiBnEUNfvzcI3pBHs4iKcbtMCQU0dB")) + '</div>').prependTo(this.$container);

        var presetRightButtons = $('<div class="n2-ss-button-container"></div>').insertAfter(this.$preset.find('.n2-css-name'));
        $('<a class="n2-button n2-button-icon n2-button-s n2-radius-s n2-button-darker n2-h5 n2-uc" href="#" data-n2tip="' + n2_('Reset design to default') + '"><i class="n2-i n2-i-reset2"></i></a>')
            .on('click', $.proxy(function (e) {
                e.preventDefault();
                this.exitPresetList(this.defs, e);
            }, this)).appendTo(presetRightButtons);

        $('<a class="n2-basiccss-save n2-button n2-button-icon n2-button-s n2-radius-s n2-button-darker n2-h5 n2-uc" href="#" data-n2tip="' + n2_('Save design as new preset') + '"><i class="n2-i n2-i-save"></i></a>')
            .on('click', $.proxy(function (e) {
                e.preventDefault();

                this.saveAsNew();
            }, this)).appendTo(presetRightButtons);


        this.$presets = $('<div id="n2-tab-basiccsspresets"></div>').appendTo(this.$container);


        $('<a class="n2-basiccss-choose n2-button n2-button-icon n2-button-s n2-radius-s n2-button-green n2-h5 n2-uc" data-n2tip="' + n2_('Load design') + '" href="#"><i class="n2-i n2-i-addlayer2"></i></a>')
            .on('click', $.proxy(function (e) {
                e.preventDefault();

                this.showList();
            }, this))
            .appendTo(presetRightButtons);

        $('<a class="n2-basiccss-back n2-button n2-button-icon n2-button-s n2-radius-s n2-button-grey n2-h5 n2-uc" href="#"><i class="n2-i n2-i-closewindow"></i></a>')
            .on('click', $.proxy(function (e) {
                e.preventDefault();
                this.exitPresetList(false, e);
            }, this))
            .appendTo(presetRightButtons);

        nextend.basicCSS = this;
    }

    BasicCSS.prototype.showList = function () {
        this.inPresetList = true;
        this.lastState = this.serialize();

        $.when(this.loadType()).done($.proxy(function (data) {
            this.$presets.append(this.storage[this.type]);

            this.$container.addClass('n2-basiccss-show-preset-list');
        }, this));


        this.$presets.on('mouseleave', $.proxy(function () {
            this.throttledUnSerialize(this.lastState);
        }, this));
    };

    BasicCSS.prototype.activate = function (type, values, structure) {

        if (this.inPresetList) {
            this.exitPresetList(false);
        }

        this.underActivate = true;
        if (this.type && this.type !== type && typeof this.storage[this.type] !== 'undefined') {
            this.storage[this.type].detach();
        }

        var hasVisuals = false;
        this.defs = {
            font: [],
            style: []
        };
        this.type = type;
        for (var k in this.assets) {
            for (var i = 0; i < structure[k].length; i++) {
                this.defs[k][structure[k][i].name] = structure[k][i].def;
            }
            this.assets[k].load(values, structure[k]);
            hasVisuals = hasVisuals || this.assets[k].hasVisuals;
        }
        $('#n2-ss-layer-window').toggleClass('n2-ss-has-design-option', hasVisuals);
        if (!hasVisuals) {
            if ($('#n2-ss-layer-window .n2-sidebar-tab-switcher .n2-td[data-tab="style"]').hasClass('n2-active')) {
                $('#n2-ss-layer-window .n2-sidebar-tab-switcher .n2-td[data-tab="item"]').trigger('click');
            }
        }
        this.underActivate = false;
    };

    BasicCSS.prototype.deActivate = function () {

        if (this.inPresetList) {
            this.exitPresetList(false);
        }
    };

    BasicCSS.prototype.serialize = function () {
        var serialized = {};
        for (var k in this.assets) {
            serialized[k] = this.assets[k].serialize();
        }
        return serialized;
    };

    BasicCSS.prototype.unSerialize = function (serialized) {
        for (var k in this.assets) {
            this.assets[k].unSerialize(serialized[k]);
        }
    };

    BasicCSS.prototype.throttledUnSerialize = function (serialized) {
        this._addThrottledRenderTimeout($.proxy(this.unSerialize, this, serialized));
    };

    BasicCSS.prototype.saveAsNew = function (name) {
        if (typeof this.saveAsModal == 'undefined') {
            var that = this;
            this.saveAsModal = new N2Classes.NextendModal({
                zero: {
                    size: [
                        500,
                        220
                    ],
                    title: n2_('Save as'),
                    close: true,
                    content: '<form class="n2-form"></form>',
                    controls: ['<a href="#" class="n2-button n2-button-normal n2-button-l n2-radius-s n2-button-green n2-uc n2-h4">' + n2_('Save as new') + '</a>'],
                    fn: {
                        show: function () {

                            var button = this.controls.find('.n2-button'),
                                form = this.content.find('.n2-form').on('submit', function (e) {
                                    e.preventDefault();
                                    button.trigger('click');
                                }).append(this.createInput(n2_('Name'), 'n2-visual-name', 'width: 446px;')),
                                nameField = this.content.find('#n2-visual-name').focus();

                            button.on('click', $.proxy(function (e) {
                                e.preventDefault();
                                var name = nameField.val();
                                if (name == '') {
                                    N2Classes.Notification.error(n2_('Please fill the name field!'));
                                } else {
                                    N2Classes.AjaxHelper.ajax({
                                        type: "POST",
                                        url: N2Classes.AjaxHelper.makeAjaxUrl(that.ajaxUrl, {
                                            nextendaction: 'addVisual'
                                        }),
                                        data: {
                                            type: that.type,
                                            value: N2Classes.Base64.encode(JSON.stringify({
                                                name: name,
                                                data: that.serialize()
                                            }))
                                        },
                                        dataType: 'json'
                                    })
                                        .done($.proxy(function (response) {

                                            $.when(that.loadType()).done(function () {
                                                that.addVisual(response.data.visual).prependTo(that.storage[that.type]);
                                            });
                                            this.hide(e);
                                        }, this));
                                }
                            }, this));
                        }
                    }
                }
            }, false);
        }
        this.saveAsModal.show();
    };

    BasicCSS.prototype.loadType = function () {
        if (typeof this.storage[this.type] === 'undefined') {
            var deferred = $.Deferred(),
                parseVisuals = $.proxy(function (visuals) {
                    this.storage[this.type] = $('<ul class="n2-list n2-h4"></ul>');
                    for (var i = 0; i < visuals.length; i++) {
                        this.addVisual(visuals[i]);
                    }
                    deferred.resolve();
                }, this);
            if (typeof window[this.type] === 'undefined') {
                this.storage[this.type] = deferred;
                N2Classes.AjaxHelper.ajax({
                    type: "POST",
                    url: N2Classes.AjaxHelper.makeAjaxUrl(this.ajaxUrl, {
                        nextendaction: 'loadVisuals'
                    }),
                    data: {
                        type: this.type
                    },
                    dataType: 'json'
                }).done($.proxy(function (response) {
                    parseVisuals(response.data.visuals);
                }, this));
            } else {
                parseVisuals(window[this.type]);
            }
        }
        return this.storage[this.type];
    };

    /**
     * loadType must be called for the actual type to be able to add visual!!!
     * @param visual
     * @returns {*}
     */
    BasicCSS.prototype.addVisual = function (visual) {

        var decoded = visual.value;
        if (decoded[0] != '{') {
            decoded = N2Classes.Base64.decode(decoded)
        }

        var value = JSON.parse(decoded),
            row = $('<li><a href="#">' + value.name + '</a></li>').on({
                mouseenter: $.proxy(function (value, e) {
                    this.throttledUnSerialize(value.data);
                }, this, value),
                click: $.proxy(function (data, e) {
                    e.preventDefault();
                    this.exitPresetList(data, e);
                }, this, value.data)
            }).appendTo(this.storage[this.type]);

        if (visual.id > 10000) {
            var actions = $('<span class="n2-actions"></span>').appendTo(row);

            $('<div class="n2-button n2-button-icon n2-button-s" data-n2tip="Overwrite preset"><i class="n2-i n2-i-save n2-i-grey-opacity"></i></div>').on('click', $.proxy(function (visualID, name, e) {
                e.stopPropagation();
                N2Classes.AjaxHelper.ajax({
                    type: "POST",
                    url: N2Classes.AjaxHelper.makeAjaxUrl(this.ajaxUrl, {
                        nextendaction: 'changeVisual'
                    }),
                    data: {
                        visualId: visualID,
                        value: N2Classes.Base64.encode(JSON.stringify({
                            name: name,
                            data: this.lastState
                        })),
                        type: this.type
                    },
                    dataType: 'json'
                }).done($.proxy(function (response) {
                    row.replaceWith(this.addVisual(response.data.visual));
                }, this));
            }, this, visual.id, value.name)).appendTo(actions);

            $('<div class="n2-button n2-button-icon n2-button-s"><i class="n2-i n2-i-delete n2-i-grey-opacity"></i></div>').on('click', $.proxy(function (visualID, e) {
                e.preventDefault();
                e.stopPropagation();
                N2Classes.AjaxHelper.ajax({
                    type: "POST",
                    url: N2Classes.AjaxHelper.makeAjaxUrl(this.ajaxUrl, {
                        nextendaction: 'deleteVisual'
                    }),
                    data: {
                        visualId: visualID,
                        type: this.type
                    },
                    dataType: 'json'
                }).done($.proxy(function (response) {
                    row.remove();
                }, this));
            }, this, visual.id)).appendTo(actions);

            nextend.tooltip.add(actions);
        }
        return row;
    };

    BasicCSS.prototype.exitPresetList = function (data, e) {
        if (this.throttleSetTimeout) {
            clearTimeout(this.throttleSetTimeout);
        }

        this.$presets.off('mouseleave');

        if (data) {
            this.inPresetList = false;
            this.unSerialize(data);
        } else {
            this.unSerialize(this.lastState);
        }
        this.$container.removeClass('n2-basiccss-show-preset-list');
        this.inPresetList = false;

    };

    BasicCSS.prototype._addThrottledRenderTimeout = function (cb) {
        if (this.throttleSetTimeout) {
            clearTimeout(this.throttleSetTimeout);
        }

        this.throttleSetTimeout = setTimeout(cb, 100);
    };

    BasicCSS.prototype._addThrottledExitTimeout = function (cb) {
        if (this.throttleExitTimeout) {
            clearTimeout(this.throttleExitTimeout);
        }

        this.throttleExitTimeout = setTimeout(cb, 100);
    };

    return BasicCSS;

});
N2D('FormElementCheckbox', ['FormElement'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param values
     * @constructor
     */
    function FormElementCheckbox(id, values) {
        this.separator = '||';

        this.element = $('#' + id);

        this.values = values;

        this.checkboxes = this.element.parent().find('.n2-checkbox-option');

        this.states = this.element.val().split(this.separator);

        for (var i = 0; i < this.checkboxes.length; i++) {
            if (typeof this.states[i] === 'undefined' || this.states[i] != this.values[i]) {
                this.states[i] = '';
            }

            this.checkboxes.eq(i).on('click', $.proxy(this.switchCheckbox, this, i));
        }

        N2Classes.FormElement.prototype.constructor.apply(this, arguments);
    }


    FormElementCheckbox.prototype = Object.create(N2Classes.FormElement.prototype);
    FormElementCheckbox.prototype.constructor = FormElementCheckbox;


    FormElementCheckbox.prototype.switchCheckbox = function (i) {
        if (this.states[i] == this.values[i]) {
            this.states[i] = '';
            this.setSelected(i, 0);
        } else {
            this.states[i] = this.values[i];
            this.setSelected(i, 1);
        }
        this.element.val(this.states.join(this.separator));

        this.triggerOutsideChange();
    };

    FormElementCheckbox.prototype.insideChange = function (values) {

        var states = values.split(this.separator);

        for (var i = 0; i < this.checkboxes.length; i++) {
            if (typeof states[i] === 'undefined' || states[i] != this.values[i]) {
                this.states[i] = '';
                this.setSelected(i, 0);
            } else {
                this.states[i] = this.values[i];
                this.setSelected(i, 1);
            }

        }

        this.element.val(this.states.join(this.separator));

        this.triggerInsideChange();
    };

    FormElementCheckbox.prototype.setSelected = function (i, state) {
        if (state) {
            this.checkboxes.eq(i)
                .addClass('n2-active');
        } else {
            this.checkboxes.eq(i)
                .removeClass('n2-active');
        }
    };


    return FormElementCheckbox;

});
N2D('FormElementColor', ['FormElement'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param alpha
     * @constructor
     */
    function FormElementColor(id, alpha) {

        this.element = $('#' + id);

        if (alpha == 1) {
            this.alpha = true;
        } else {
            this.alpha = false;
        }
        this.element.off('change')
            .n2spectrum({
                showAlpha: this.alpha,
                preferredFormat: (this.alpha == 1 ? "hex8" : "hex6"),
                showInput: false,
                showButtons: false,
                move: $.proxy(this.onMove, this),
                showSelectionPalette: true,
                showPalette: true,
                maxSelectionSize: 6,
                localStorageKey: 'color',
                palette: [
                    ['000000', '55aa39', '357cbd', 'bb4a28', '8757b2', '000000CC'],
                    ['81898d', '5cba3c', '4594e1', 'd85935', '9e74c2', '00000080'],
                    ['ced3d5', '27ae60', '01add3', 'e79d19', 'e264af', 'FFFFFFCC'],
                    ['ffffff', '2ecc71', '00c1c4', 'ecc31f', 'ec87c0', 'FFFFFF80']
                ]
            })
            .on('change', $.proxy(this.onChange, this));

        this.text = this.element.data('field');

        N2Classes.FormElement.prototype.constructor.apply(this, arguments);
    };

    FormElementColor.prototype = Object.create(N2Classes.FormElement.prototype);
    FormElementColor.prototype.constructor = FormElementColor;

    FormElementColor.prototype.onMove = function () {
        this.element.val(this.getCurrent());
        this.triggerOutsideChange();
    };

    FormElementColor.prototype.onChange = function (e) {
        var current = this.getCurrent(),
            value = this.element.val();
        if (current != value) {
            if (value.length > 0 && value.charAt(0) != '{') {
                this.element.n2spectrum("set", value);
            } else if (value.length === 0) {
                // If the field left blank, we need to fix it
                value = (this.alpha == 1 ? "00000000" : "000000");
                this.element.val(value);
                this.element.n2spectrum("set", value);
            }

            this.triggerInsideChange();
            this.triggerOutsideChange();
        }
        e.stopImmediatePropagation();
    };

    FormElementColor.prototype.insideChange = function (value) {
        this.element.val(value);
        this.element.n2spectrum("set", value);

        this.triggerInsideChange();
    };

    FormElementColor.prototype.getCurrent = function () {
        if (this.alpha) {
            return this.element.n2spectrum("get").toHexString8();
        }
        return this.element.n2spectrum("get").toHexString(true);
    };

    return FormElementColor;

});
N2D('FormElementDevice', ['FormElementOnoff'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @constructor
     */
    function FormElementDevice(id) {
        N2Classes.FormElementOnoff.prototype.constructor.apply(this, arguments);
    }

    FormElementDevice.prototype = Object.create(N2Classes.FormElementOnoff.prototype);
    FormElementDevice.prototype.constructor = FormElementDevice;

    FormElementDevice.prototype.detach = function () {
        this.onoff.detach();
    };

    FormElementDevice.prototype.setSelected = function (state) {
        if (state) {
            this.onoff.addClass('n2-active');
        } else {
            this.onoff.removeClass('n2-active');
        }
    };

    return FormElementDevice;
});
N2D('FormElementDevices', ['FormElementDevice'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param values
     * @constructor
     */
    function FormElementDevices(id, values) {

        this.$el = $('#' + id).data('field', this);
        this.fields = {};
        for (var i = 0; i < values.length; i++) {
            this.fields[values[i]] = new N2Classes.FormElementDevice(id + '-' + values[i]);
        }
    }

    FormElementDevices.prototype.setAvailableDevices = function (devices) {
        for (var k in devices) {
            var field = this.fields[k.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()];
            if (!devices[k]) {
                field.detach();
            }
        }
        this.$el.children().first().addClass('n2-first');
        this.$el.children().last().addClass('n2-last');
    };

    return FormElementDevices;
});
N2D('FormElementFolders', ['FormElement'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param parameters
     * @constructor
     */
    function FormElementFolders(id, parameters) {
        this.element = $('#' + id);

        this.field = this.element.data('field');

        this.parameters = parameters;

        this.editButton = $('#' + id + '_edit')
            .on('click', $.proxy(this.edit, this));

        this.button = $('#' + id + '_button').on('click', $.proxy(this.open, this));

        this.element.siblings('.n2-form-element-clear')
            .on('click', $.proxy(this.clear, this));

        N2Classes.FormElement.prototype.constructor.apply(this, arguments);
    }

    FormElementFolders.prototype = Object.create(N2Classes.FormElement.prototype);
    FormElementFolders.prototype.constructor = FormElementFolders;

    FormElementFolders.prototype.clear = function (e) {
        e.preventDefault();
        e.stopPropagation();
        this.val('');
    };

    FormElementFolders.prototype.val = function (value) {
        this.element.val(value);
        this.triggerOutsideChange();
    };

    FormElementFolders.prototype.open = function (e) {
        e.preventDefault();
        nextend.imageHelper.openFoldersLightbox($.proxy(this.val, this));
    };

    return FormElementFolders;
});
N2D('FormElementFont', ['FormElement'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param parameters
     * @constructor
     */
    function FormElementFont(id, parameters) {
        this.element = $('#' + id);

        this.parameters = parameters;

        this.defaultSetId = parameters.set;

        this.element.parent()
            .on('click', $.proxy(this.show, this));

        this.element.siblings('.n2-form-element-clear')
            .on('click', $.proxy(this.clear, this));

        this.name = this.element.siblings('input');

        nextend.fontManager.$.on('visualDelete', $.proxy(this.fontDeleted, this));

        this.updateName(this.element.val());

        N2Classes.FormElement.prototype.constructor.apply(this, arguments);
    }

    FormElementFont.prototype = Object.create(N2Classes.FormElement.prototype);
    FormElementFont.prototype.constructor = FormElementFont;


    FormElementFont.prototype.getLabel = function () {
        return this.parameters.label;
    };

    FormElementFont.prototype.show = function (e) {
        e.preventDefault();
        if (this.parameters.style != '') {
            nextend.fontManager.setConnectedStyle(this.parameters.style);
        }
        if (this.parameters.style2 != '') {
            nextend.fontManager.setConnectedStyle2(this.parameters.style2);
        }
        if (this.defaultSetId) {
            nextend.fontManager.changeSetById(this.defaultSetId);
        }
        nextend.fontManager.show(this.element.val(), $.proxy(this.save, this), {
            previewMode: this.parameters.previewmode,
            previewHTML: this.parameters.preview
        });
    };

    FormElementFont.prototype.clear = function (e) {
        e.preventDefault();
        e.stopPropagation();
        this.val('');
    };

    FormElementFont.prototype.save = function (e, value) {

        nextend.fontManager.addVisualUsage(this.parameters.previewmode, value, window.nextend.pre);

        this.val(value);
    };

    FormElementFont.prototype.val = function (value) {
        this.element.val(value);
        this.updateName(value);
        this.triggerOutsideChange();
    };

    FormElementFont.prototype.insideChange = function (value) {
        this.element.val(value);

        this.updateName(value);

        this.triggerInsideChange();
    };

    FormElementFont.prototype.updateName = function (value) {
        $.when(nextend.fontManager.getVisual(value))
            .done($.proxy(function (font) {
                this.name.val(font.name);
            }, this));
    };
    FormElementFont.prototype.fontDeleted = function (e, id) {
        if (id == this.element.val()) {
            this.insideChange('');
        }
    };

    FormElementFont.prototype.renderFont = function () {
        var font = this.element.val();
        nextend.fontManager.addVisualUsage(this.parameters.previewmode, font, '');
        return nextend.fontManager.getClass(font, this.parameters.previewmode);
    };

    return FormElementFont;
});
N2D('FormElementIcon2Manager', ['FormElement'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @constructor
     */
    function FormElementIcon2Manager(id) {
        this.element = $('#' + id);
        this.button = $('#' + id + '_edit').on('click', $.proxy(this.openModal, this));

        this.preview = this.element.parent().find('.n2-form-element-preview').on('click', $.proxy(this.openModal, this));

        this.element.on('nextendChange', $.proxy(this.makePreview, this));


        N2Classes.FormElement.prototype.constructor.apply(this, arguments);

        this.element.siblings('.n2-form-element-clear')
            .on('click', $.proxy(this.clear, this));
    }

    FormElementIcon2Manager.prototype = Object.create(N2Classes.FormElement.prototype);
    FormElementIcon2Manager.prototype.constructor = FormElementIcon2Manager;

    FormElementIcon2Manager.prototype.clear = function (e) {
        e.preventDefault();
        e.stopPropagation();
        this.val('');
    };

    FormElementIcon2Manager.prototype.insideChange = function (value) {
        this.element.val(value);

        this.triggerInsideChange();
    };

    FormElementIcon2Manager.prototype.openModal = function (e) {
        if (e) e.preventDefault();
        N2Classes.Icons.showModal($.proxy(this.setIcon, this), this.element.val());
    };

    FormElementIcon2Manager.prototype.val = function (value) {
        this.element.val(value);
        this.triggerOutsideChange();
    };

    FormElementIcon2Manager.prototype.setIcon = function (value) {
        this.val(value);
    };

    FormElementIcon2Manager.prototype.makePreview = function () {
        var iconData = N2Classes.Icons.render(this.element.val());
        if (iconData) {
            this.preview.html('<i class="n2i ' + iconData.class + '">' + iconData.ligature + '</i>');
        } else {
            this.preview.html('');
        }
    };

    FormElementIcon2Manager.prototype.focus = function (shouldOpen) {
        if (shouldOpen) {
            this.openModal();
        }
    };


    return FormElementIcon2Manager;
});
N2D('FormElementIconManager', ['FormElement'], function ($, undefined) {
    var modal = null,
        callback = function () {
        };

    function getIconModal() {
        if (!modal) {
            var content = '';

            modal = new N2Classes.NextendModal({
                zero: {
                    size: [
                        1200,
                        600
                    ],
                    title: 'Icons',
                    back: false,
                    close: true,
                    content: content,
                    fn: {
                        show: function () {

                            var icons = this.content.find('.n2-icon');
                            icons.on('click', $.proxy(function (e) {
                                var node = $(e.currentTarget).clone(),
                                    svg = node.find('svg');

                                if (svg[0].hasChildNodes()) {
                                    var children = svg[0].childNodes;
                                    for (var i = 0; i < children.length; i++) {
                                        children[i].setAttribute("data-style", "{style}");
                                    }
                                }
                                callback(node.html());
                                this.hide(e);
                            }, this));
                        }
                    }
                }
            }, false);
            modal.setCustomClass('n2-icons-modal');
        }
        return modal;
    }

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @constructor
     */
    function FormElementIconManager(id) {
        this.element = $('#' + id);
        this.button = $('#' + id + '_edit').on('click', $.proxy(this.openModal, this));

        this.preview = this.element.parent().find('img').on('click', $.proxy(this.openModal, this));

        this.element.on('nextendChange', $.proxy(this.makePreview, this));


        N2Classes.FormElement.prototype.constructor.apply(this, arguments);
    }


    FormElementIconManager.prototype = Object.create(N2Classes.FormElement.prototype);
    FormElementIconManager.prototype.constructor = FormElementIconManager;

    FormElementIconManager.prototype.insideChange = function (value) {
        this.element.val(value);

        this.triggerInsideChange();
    };

    FormElementIconManager.prototype.openModal = function (e) {
        if (e) e.preventDefault();
        callback = $.proxy(this.setIcon, this);
        getIconModal().show();
    };

    FormElementIconManager.prototype.val = function (value) {
        this.element.val(value);
        this.triggerOutsideChange();
    };

    FormElementIconManager.prototype.setIcon = function (svg) {
        this.val(svg);
    };

    FormElementIconManager.prototype.makePreview = function () {
        this.preview.attr('src', 'data:image/svg+xml;base64,' + N2Classes.Base64.encode(this.element.val()));
    };

    FormElementIconManager.prototype.focus = function (shouldOpen) {
        if (shouldOpen) {
            this.openModal();
        }
    };

    function LZWDecompress(compressed) {
        "use strict";
        // Build the dictionary.
        var i,
            dictionary = [],
            w,
            result,
            k,
            entry = "",
            dictSize = 256;
        for (i = 0; i < 256; i += 1) {
            dictionary[i] = String.fromCharCode(i);
        }

        w = String.fromCharCode(compressed[0]);
        result = w;
        for (i = 1; i < compressed.length; i += 1) {
            k = compressed[i];
            if (dictionary[k]) {
                entry = dictionary[k];
            } else {
                if (k === dictSize) {
                    entry = w + w.charAt(0);
                } else {
                    return null;
                }
            }

            result += entry;

            // Add w+entry[0] to the dictionary.
            dictionary[dictSize++] = w + entry.charAt(0);

            w = entry;
        }
        return result;
    }

    return FormElementIconManager;


});
N2D('FormElementImage', ['FormElement'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param parameters
     * @constructor
     */
    function FormElementImage(id, parameters) {
        this.element = $('#' + id);

        this.field = this.element.data('field');
        this.field.connectedField = this;

        this.parameters = parameters;

        this.preview = $('#' + id + '_preview')
            .on('click', $.proxy(this.open, this));

        this.element.on('nextendChange', $.proxy(this.makePreview, this));

        this.button = $('#' + id + '_button').on('click', $.proxy(this.open, this));

        this.element.siblings('.n2-form-element-clear')
            .on('click', $.proxy(this.clear, this));
    }


    FormElementImage.prototype = Object.create(N2Classes.FormElement.prototype);
    FormElementImage.prototype.constructor = FormElementImage;

    FormElementImage.prototype.clear = function (e) {
        e.preventDefault();
        e.stopPropagation();
        this.val('');
    };

    FormElementImage.prototype.val = function (value, meta) {
        meta = $.extend({alt: false}, meta);
        if (meta.alt && meta.alt !== '' && this.parameters.alt && this.parameters.alt !== '') {
            $('#' + this.parameters.alt).val(meta.alt).trigger('change');
        }
        this.element.val(value);
        this.triggerOutsideChange();
    };

    FormElementImage.prototype.makePreview = function () {
        var image = this.element.val();
        if (image.substr(0, 1) === '{') {
            this.preview.css('background-image', '');
        } else {
            this.preview.css('background-image', 'url(' + nextend.imageHelper.fixed(image) + ')');
        }
    };

    FormElementImage.prototype.open = function (e) {
        if (e) {
            e.preventDefault();
        }
        nextend.imageHelper.openLightbox($.proxy(this.val, this));
    };

    FormElementImage.prototype.focus = function (shouldOpen) {
        if (shouldOpen) {
            this.open();
        }
    };

    return FormElementImage;
});
N2D('FormElementImageManager', ['FormElement'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param parameters
     * @constructor
     */
    function FormElementImageManager(id, parameters) {
        this.element = $('#' + id);
        $('#' + id + '_manage').on('click', $.proxy(this.show, this));

        this.parameters = parameters;
        this.imageField = this.element.data('field');

        N2Classes.FormElement.prototype.constructor.apply(this, arguments);
    }


    FormElementImageManager.prototype = Object.create(N2Classes.FormElement.prototype);
    FormElementImageManager.prototype.constructor = FormElementImageManager;


    FormElementImageManager.prototype.show = function (e) {
        e.preventDefault();
        nextend.imageManager.show(this.element.val(), $.proxy(this.save, this));
    };

    FormElementImageManager.prototype.save = function () {

    };

    FormElementImageManager.prototype.insideChange = function (value) {
        this.element.val(value);

        this.triggerInsideChange();
    };

    return FormElementImageManager;

});
N2D('FormElementList', ['FormElement'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param multiple
     * @param relatedFields
     * @constructor
     */
    function FormElementList(id, multiple, relatedFields, relatedValueFields) {

        this.separator = '||';

        this.element = $('#' + id).on('change', $.proxy(this.onHiddenChange, this));

        this.select = $('#' + id + '_select').on('change', $.proxy(this.onChange, this));

        this.multiple = multiple;

        this.relatedFields = false;
        if (relatedFields !== undefined && relatedFields.length) {
            this.relatedFields = $('');
            for (var i = 0; i < relatedFields.length; i++) {
                this.relatedFields = this.relatedFields.add($('[data-field="' + relatedFields[i] + '"]'));
            }

            this.relatedFields.toggleClass('n2-hidden', this.isOff(this.element.val()));
        }

        this.relatedValueFields = false;
        if (relatedValueFields !== undefined && relatedValueFields.length) {
            var value = this.element.val();
            this.relatedValueFields = $('');
            for (var i = 0; i < relatedValueFields.length; i++) {
                var $field = $('[data-field="' + relatedValueFields[i].field + '"]')
                    .data('show-values', relatedValueFields[i].values);

                $field.toggleClass('n2-hidden', $.inArray(value, relatedValueFields[i].values) === -1);

                this.relatedValueFields = this.relatedValueFields.add($field);
            }

        }

        N2Classes.FormElement.prototype.constructor.apply(this, arguments);
    }


    FormElementList.prototype = Object.create(N2Classes.FormElement.prototype);
    FormElementList.prototype.constructor = FormElementList;

    FormElementList.prototype.onHiddenChange = function () {
        var value = this.element.val();
        if (value && value != this.select.val()) {
            this.insideChange(value);
        }
    };

    FormElementList.prototype.onChange = function () {
        var value = this.select.val();
        if (value !== null && typeof value === 'object') {
            value = value.join(this.separator);
        }
        this.setHiddenValue(value);

        this.triggerOutsideChange();
    };

    FormElementList.prototype.insideChange = function (value) {
        if (typeof value === 'object') {
            this.select.val(value.split(this.separator));
        } else {
            this.select.val(value);
        }
        this.setHiddenValue(value);
        this.select.val(value);

        this.triggerInsideChange();
    };

    FormElementList.prototype.setHiddenValue = function (value) {
        this.element.val(value);

        if (this.relatedFields) {
            this.relatedFields.toggleClass('n2-hidden', this.isOff(value));
        }

        if (this.relatedValueFields) {
            this.relatedValueFields.each(function () {
                var $el = $(this);
                $el.toggleClass('n2-hidden', $.inArray(value, $el.data('show-values')) === -1);
            });
        }
    };

    FormElementList.prototype.isOff = function (value) {
        return value == '' || value == '0' || value == 'off';
    };

    return FormElementList;

});

N2D('FormElementMarginPadding', ['FormElementMixed'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param elements
     * @param separator
     * @constructor
     */
    function FormElementMarginPadding(id, elements, separator) {
        this.linkedValues = false;

        N2Classes.FormElementMixed.prototype.constructor.apply(this, arguments);

        this.$field = this.element.parent();

        this.$field.find('.n2-text-sub-label').on('click', $.proxy(function (e) {
            e.preventDefault();
            this.linkedValues = !this.linkedValues;

            this.$field.toggleClass('n2-values-linked', this.linkedValues);

            if (this.linkedValues) {
                this.elements[0].trigger('change');
            }
        }, this));
    }


    FormElementMarginPadding.prototype = Object.create(N2Classes.FormElementMixed.prototype);
    FormElementMarginPadding.prototype.constructor = FormElementMarginPadding;

    FormElementMarginPadding.prototype.onFieldChange = function () {
        if (this.linkedValues) {
            var value = this.elements[0].val();
            for (var i = 1; i < 4; i++) {
                this.elements[i].data('field').insideChange(value);
            }
        }

        this.element.val(this.getValue());
        this.triggerOutsideChange();
    };

    FormElementMarginPadding.prototype.insideChange = function (value) {
        N2Classes.FormElementMixed.prototype.insideChange.apply(this, arguments);

        this.linkedValues = true;
        var value = this.elements[0].val();
        for (var i = 1; i < 4; i++) {
            if (value != this.elements[i].val()) {
                this.linkedValues = false;
                break;
            }
        }

        this.$field.toggleClass('n2-values-linked', this.linkedValues);
    };

    return FormElementMarginPadding;
});




N2D('FormElementMixed', ['FormElement'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param elements
     * @param separator
     * @constructor
     */
    function FormElementMixed(id, elements, separator) {

        this.element = $('#' + id);

        this.elements = [];
        for (var i = 0; i < elements.length; i++) {
            this.elements.push($('#' + elements[i])
                .on('outsideChange', $.proxy(this.onFieldChange, this)));
        }

        this.separator = separator;

        N2Classes.FormElement.prototype.constructor.apply(this, arguments);
    }


    FormElementMixed.prototype = Object.create(N2Classes.FormElement.prototype);
    FormElementMixed.prototype.constructor = FormElementMixed;


    FormElementMixed.prototype.onFieldChange = function () {
        this.element.val(this.getValue());

        this.triggerOutsideChange();
    };

	FormElementMixed.prototype.insideChange = function (value) {
		if (typeof value === "string" && value.indexOf(this.separator) !== -1) {
			this.element.val(value);

			var values = value.split(this.separator);

			for (var i = 0; i < this.elements.length; i++) {
				this.elements[i].data('field').insideChange(values[i]);
			}

			this.triggerInsideChange();
		}
	};

    FormElementMixed.prototype.getValue = function () {
        var values = [];
        for (var i = 0; i < this.elements.length; i++) {
            values.push(this.elements[i].val());
        }

        return values.join(this.separator);
    };

    return FormElementMixed;

});
N2D('FormElementNumberSlider', function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param properties
     * @constructor
     */
    function FormElementNumberSlider(id, properties) {
        this.localChange = false;
        this.element = $('#' + id).data('autocomplete', this);
        var $parent = this.element.parent().on({
            'mouseenter.n2slider': $.proxy(this.startSlider, this, properties)
        });
        var $units = $parent.siblings('.n2-form-element-units').find('> input');
        if (properties.units && $units.length) {
            var units = properties.units;
            $units.on('nextendChange', $.proxy(function () {
                properties.min = units[$units.val() + 'Min'];
                properties.max = units[$units.val() + 'SliderMax'];
                if (this.slider) {
                    this.slider.nUISlider("option", "min", properties.min);
                    this.slider.nUISlider("option", "max", properties.max);
                }
            }, this));
        }
    }

    FormElementNumberSlider.prototype.startSlider = function (properties, e) {
        this.element.parent().off('.n2slider');
        if (!this.slider) {
            this.slider = $('<div></div>')
                .appendTo($('<div class="nui-slider-container"></div>').insertAfter(this.element))
                .removeAttr('slide').prop('slide', false).nUISlider($.extend({
                    start: $.proxy(function () {
                        this.element.parent().addClass('n2-active');
                    }, this),
                    stop: $.proxy(function () {
                        this.element.parent().removeClass('n2-active');
                    }, this),
                    slide: $.proxy(function (e, ui) {
                        this.localChange = true;
                        this.element.val(ui.value).trigger('change');
                        this.localChange = false;
                    }, this)
                }, properties));

            if (typeof this.slider[0].slide !== 'undefined') {
                this.slider[0].slide = null;
            }

            this.element.on('nextendChange', $.proxy(function () {
                if (!this.localChange) {
                    var val = this.element.val();
                    if (val == parseFloat(val)) {
                        this.slider.nUISlider("option", 'value', parseFloat(this.element.val()));
                    }
                }
            }, this));
        }
        this.slider.nUISlider("option", 'value', parseFloat(this.element.val()));
    };

    return FormElementNumberSlider;
});
N2D('FormElementNumber', ['FormElement'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param min
     * @param max
     * @param units
     * @constructor
     */
    function FormElementNumber(id, min, max, units) {
        this.min = min;
        this.max = max;

        this.element = $('#' + id).on({
            focus: $.proxy(this._focus, this),
            blur: $.proxy(this.blur, this),
            change: $.proxy(this.change, this)
        });
        this.parent = this.element.parent();

        var $units = this.parent.siblings('.n2-form-element-units').find('> input');
        if (units && $units.length) {
            $units.on('nextendChange', $.proxy(function () {
                this.min = units[$units.val() + 'Min'];
                this.max = units[$units.val() + 'Max'];
            }, this));
        }

        N2Classes.FormElement.prototype.constructor.apply(this, arguments);
    }


    FormElementNumber.prototype = Object.create(N2Classes.FormElement.prototype);
    FormElementNumber.prototype.constructor = FormElementNumber;


    FormElementNumber.prototype._focus = function () {
        this.parent.addClass('focus');

        this.element.on('keypress.n2-text', $.proxy(function (e) {
            if (e.which == 13) {
                this.element.off('keypress.n2-text');
                this.element.trigger('blur');
            }
        }, this));
    };

    FormElementNumber.prototype.blur = function () {
        this.parent.removeClass('focus');
    };

    FormElementNumber.prototype.change = function () {
        var validated = this.validate(this.element.val());
        if (validated === true) {
            this.triggerOutsideChange();
        } else {
            this.element.val(validated).trigger('change');
        }
    };

    FormElementNumber.prototype.insideChange = function (value) {
        var validated = this.validate(value);
        if (validated === true) {
            this.element.val(value);
        } else {
            this.element.val(validated);
        }

        this.triggerInsideChange();
    };

    FormElementNumber.prototype.validate = function (value) {
        var validatedValue = parseFloat(value);
        if (isNaN(validatedValue)) {
            validatedValue = 0;
        }
        validatedValue = Math.max(this.min, Math.min(this.max, validatedValue));
        if (validatedValue != value) {
            return validatedValue;
        }
        return true;
    };

    return FormElementNumber;
});
N2D('FormElementOnoff', ['FormElement'], function ($, undefined) {

    /**
     * @typedef {{relatedFields: array, relatedAttribute: string}} options
     */
    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param isEnable
     * @param {options} options
     * @constructor
     */
    function FormElementOnoff(id, isEnable, options) {
        this.element = $('#' + id);

        this.isEnable = !!isEnable;
        this.relatedFields = $('');
        if (options !== undefined) {
            if (options.relatedFields !== undefined && options.relatedFields.length) {
                for (var i = 0; i < options.relatedFields.length; i++) {
                    this.relatedFields = this.relatedFields.add($('[data-field="' + options.relatedFields[i] + '"]'));
                }
            }

            if (options.relatedAttribute !== undefined && options.relatedAttribute !== '') {
                var $body = $('#n2-admin');
                $body.attr('data-' + options.relatedAttribute, this.element.val());
                this.element.on('nextendChange', $.proxy(function () {
                    $body.attr('data-' + options.relatedAttribute, this.element.val());
                }, this))
            }
        }

        this.onoff = this.element.parent()
            .on('click', $.proxy(this.switch, this));

        if (!this.onoff.hasClass('n2-onoff-on')) {
            this.relatedFields.toggleClass('n2-hidden', this.isEnable);
        } else {
            this.relatedFields.toggleClass('n2-hidden', !this.isEnable);
        }

        N2Classes.FormElement.prototype.constructor.apply(this, arguments);
    }


    FormElementOnoff.prototype = Object.create(N2Classes.FormElement.prototype);
    FormElementOnoff.prototype.constructor = FormElementOnoff;


    FormElementOnoff.prototype.switch = function () {
        var value = parseInt(this.element.val());
        if (value) {
            value = 0;
        } else {
            value = 1;
        }
        this.element.val(value);
        this.setSelected(value);

        this.triggerOutsideChange();
    };

    FormElementOnoff.prototype.insideChange = function (value) {
        value = parseInt(value);
        this.element.val(value);
        this.setSelected(value);

        this.triggerInsideChange();
    };

    FormElementOnoff.prototype.setSelected = function (state) {
        if (state) {
            this.onoff.addClass('n2-onoff-on');
            this.relatedFields.toggleClass('n2-hidden', !this.isEnable);
        } else {
            this.onoff.removeClass('n2-onoff-on');
            this.relatedFields.toggleClass('n2-hidden', this.isEnable);
        }
    };

    return FormElementOnoff;

});

N2D('FormElementRadio', ['FormElement'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param values
     * @constructor
     */
    function FormElementRadio(id, values, relatedFields) {
        this.element = $('#' + id);

        this.values = values;
        this.relatedFields = false;
        if (relatedFields !== undefined && relatedFields.length) {
            this.relatedFields = $('');
            for (var i = 0; i < relatedFields.length; i++) {
                this.relatedFields = this.relatedFields.add($('[data-field="' + relatedFields[i] + '"]'));
            }

            this.relatedFields.toggleClass('n2-hidden', this.isOff(this.element.val()));
        }

        this.parent = this.element.parent();

        this.options = this.parent.find('.n2-radio-option');

        for (var i = 0; i < this.options.length; i++) {
            this.options.eq(i).on('click', $.proxy(this.click, this));
        }

        N2Classes.FormElement.prototype.constructor.apply(this, arguments);
    }

    FormElementRadio.prototype = Object.create(N2Classes.FormElement.prototype);
    FormElementRadio.prototype.constructor = FormElementRadio;

    FormElementRadio.prototype.click = function (e) {
        this.changeSelectedIndex(this.options.index(e.currentTarget));
    };

    FormElementRadio.prototype.changeSelectedIndex = function (index) {
        var value = this.values[index];

        this.setValue(value);

        this.setSelected(index);

        this.triggerOutsideChange();
    };

    FormElementRadio.prototype.insideChange = function (value, option) {
        var index = $.inArray(value, this.values);
        if (index == '-1') {
            index = this.partialSearch(value);
        }

        if (index == '-1' && typeof option !== 'undefined') {
            index = this.addOption(value, option);
        }

        if (index != '-1') {
            this.setValue(this.values[index]);
            this.setSelected(index);

            this.triggerInsideChange();
        } else {
            // It will reset the state if the preferred value not available
            this.options.eq(0).trigger('click');
        }
    };

    FormElementRadio.prototype.setSelected = function (index) {
        this.options.removeClass('n2-active');
        this.options.eq(index).addClass('n2-active');
    };

    FormElementRadio.prototype.partialSearch = function (text) {
        text = text.replace(/^.*[\\\/]/, '');
        for (var i = 0; i < this.values.length; i++) {
            if (this.values[i].indexOf(text) != -1) return i;
        }
        return -1;
    };

    FormElementRadio.prototype.addOption = function (value, option) {
        var i = this.values.push(value) - 1;
        option.appendTo(this.parent)
            .on('click', $.proxy(this.click, this));
        this.options = this.options.add(option);
        return i;
    };

    FormElementRadio.prototype.addTabOption = function (value, label) {
        var i = this.values.push(value) - 1;
        var option = $('<div class="n2-radio-option n2-h4 n2-last">' + label + '</div>')
            .insertAfter(this.options.last().removeClass('n2-last'))
            .on('click', $.proxy(this.click, this));
        this.options = this.options.add(option);
        return i;
    };

    FormElementRadio.prototype.removeTabOption = function (value) {
        var i = $.inArray(value, this.values);
        var option = this.options.eq(i);
        this.options = this.options.not(option);
        option.remove();
        if (i == 0) {
            this.options.eq(0).addClass('n2-first');
        }
        if (i == this.options.length) {
            this.options.eq(this.options.length - 1).addClass('n2-last');
        }

        this.values.splice(i, 1);
    };

    FormElementRadio.prototype.moveTab = function (originalIndex, targetIndex) {

    };

    FormElementRadio.prototype.setValue = function (value) {
        this.element.val(value);

        if (this.relatedFields) {
            this.relatedFields.toggleClass('n2-hidden', this.isOff(value));
        }
    };

    FormElementRadio.prototype.isOff = function (value) {
        return value === '' || value === '0' || value === 0 || value === 'off';
    };

    return FormElementRadio;

});
N2D('FormRelatedFields', function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param relatedFields
     * @constructor
     */
    function FormRelatedFields(id, relatedFields) {

        this.$field = $('#' + id);
        this.field = this.$field.data('field');

        if (this.field && this.field.relatedFieldsOff !== undefined) {
            this.fieldChanged = this.fieldChangedCallback;
        } else {
            this.fieldChanged = this.fieldChangedSimple;
        }

        this.$field.on('nextendChange', $.proxy(this.fieldChanged, this))

        this.relatedFields = $('');
        for (var i = 0; i < relatedFields.length; i++) {
            this.relatedFields = this.relatedFields.add($('[data-field="' + relatedFields[i] + '"]'));
        }

        this.fieldChanged();
    }

    FormRelatedFields.prototype.fieldChangedSimple = function () {
        var value = this.$field.val();

        this.relatedFields.toggleClass('n2-hidden', value === '');
    };

    FormRelatedFields.prototype.fieldChangedCallback = function () {
        this.relatedFields.toggleClass('n2-hidden', this.field.relatedFieldsOff());
    };

    return FormRelatedFields;
});

N2D('FormElementRichText', ['FormElementText'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @constructor
     */
    function FormElementRichText(id) {

        N2Classes.FormElementText.prototype.constructor.apply(this, arguments);

        this.parent.find('.n2-textarea-rich-bold').on('click', $.proxy(this.bold, this));
        this.parent.find('.n2-textarea-rich-italic').on('click', $.proxy(this.italic, this));
        this.parent.find('.n2-textarea-rich-link').on('click', $.proxy(this.link, this));

    }


    FormElementRichText.prototype = Object.create(N2Classes.FormElementText.prototype);
    FormElementRichText.prototype.constructor = FormElementRichText;


    FormElementRichText.prototype.bold = function () {
        this.wrapText('<b>', '</b>');
    };

    FormElementRichText.prototype.italic = function () {
        this.wrapText('<i>', '</i>');
    };

    FormElementRichText.prototype.link = function () {
        this.wrapText('<a href="#">', '</a>');
    };

    FormElementRichText.prototype.list = function () {
        this.wrapText('', "\n<ul>\n<li>#1 Item</li>\n<li>#2 Item</li>\n</ul>\n");
    };


    FormElementRichText.prototype.wrapText = function (openTag, closeTag) {
        var textArea = this.element;
        var len = textArea.val().length;
        var start = textArea[0].selectionStart;
        var end = textArea[0].selectionEnd;
        var selectedText = textArea.val().substring(start, end);
        var replacement = openTag + selectedText + closeTag;
        textArea.val(textArea.val().substring(0, start) + replacement + textArea.val().substring(end, len));
        this.triggerOutsideChange();
        this.element.focus();
    };

    return FormElementRichText;
});
N2D('FormElementSkin', ['FormElement'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param preId
     * @param skins
     * @param fixedMode
     * @constructor
     */
    function FormElementSkin(id, preId, skins, fixedMode) {
        this.element = $('#' + id);

        this.preId = preId;

        this.skins = skins;

        this.list = this.element.data('field');

        this.fixedMode = fixedMode;

        this.firstOption = this.list.select.find('option').eq(0);

        this.originalText = this.firstOption.text();

        this.element.on('nextendChange', $.proxy(this.onSkinSelect, this));

        N2Classes.FormElement.prototype.constructor.apply(this, arguments);
    }


    FormElementSkin.prototype = Object.create(N2Classes.FormElement.prototype);
    FormElementSkin.prototype.constructor = FormElementSkin;


    FormElementSkin.prototype.onSkinSelect = function () {
        var skin = this.element.val();
        if (skin != '0') {
            skin = this.skins[skin].settings;
            for (var k in skin) {
                if (skin.hasOwnProperty(k)) {
                    var el = $('#' + this.preId + k);
                    if (el.length) {
                        var field = el.data('field');
                        field.insideChange(skin[k]);
                    }
                }
            }

            if (!this.fixedMode) {
                this.changeFirstOptionText(n2_('Done'));
                this.list.insideChange('0');
                setTimeout($.proxy(this.changeFirstOptionText, this, this.originalText), 3000);
            }

        }
    };

    FormElementSkin.prototype.changeFirstOptionText = function (text) {
        this.firstOption.text(text);
    };

    FormElementSkin.prototype.insideChange = function (value) {
        this.element.val(value);
        this.list.select.val(value);
    };

    return FormElementSkin;
});

N2D('FormElementStyle', ['FormElement'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param parameters
     * @constructor
     */
    function FormElementStyle(id, parameters) {
        this.element = $('#' + id);

        this.parameters = parameters;

        this.defaultSetId = parameters.set;

        this.element.parent()
            .on('click', $.proxy(this.show, this));

        this.element.siblings('.n2-form-element-clear')
            .on('click', $.proxy(this.clear, this));

        this.name = this.element.siblings('input');

        nextend.styleManager.$.on('visualDelete', $.proxy(this.styleDeleted, this));

        this.updateName(this.element.val());
        N2Classes.FormElement.prototype.constructor.apply(this, arguments);
    }

    FormElementStyle.prototype = Object.create(N2Classes.FormElement.prototype);
    FormElementStyle.prototype.constructor = FormElementStyle;

    FormElementStyle.prototype.getLabel = function () {
        return this.parameters.label;
    };

    FormElementStyle.prototype.show = function (e) {
        e.preventDefault();
        if (this.parameters.font != '') {
            nextend.styleManager.setConnectedFont(this.parameters.font);
        }
        if (this.parameters.font2 != '') {
            nextend.styleManager.setConnectedFont2(this.parameters.font2);
        }
        if (this.parameters.style2 != '') {
            nextend.styleManager.setConnectedStyle(this.parameters.style2);
        }
        if (this.defaultSetId) {
            nextend.styleManager.changeSetById(this.defaultSetId);
        }
        nextend.styleManager.show(this.element.val(), $.proxy(this.save, this), {
            previewMode: this.parameters.previewmode,
            previewHTML: this.parameters.preview
        });
    };

    FormElementStyle.prototype.clear = function (e) {
        e.preventDefault();
        e.stopPropagation();
        this.val('');
    };

    FormElementStyle.prototype.save = function (e, value) {

        nextend.styleManager.addVisualUsage(this.parameters.previewmode, value, window.nextend.pre);

        this.val(value);
    };

    FormElementStyle.prototype.val = function (value) {
        this.element.val(value);
        this.updateName(value);
        this.triggerOutsideChange();
    };

    FormElementStyle.prototype.insideChange = function (value) {
        this.element.val(value);

        this.updateName(value);

        this.triggerInsideChange();
    };

    FormElementStyle.prototype.updateName = function (value) {
        $.when(nextend.styleManager.getVisual(value))
            .done($.proxy(function (style) {
                this.name.val(style.name);
            }, this));
    };
    FormElementStyle.prototype.styleDeleted = function (e, id) {
        if (id == this.element.val()) {
            this.insideChange('');
        }
    };
    FormElementStyle.prototype.renderStyle = function () {
        var style = this.element.val();
        nextend.styleManager.addVisualUsage(this.parameters.previewmode, style, '');
        return nextend.styleManager.getClass(style, this.parameters.previewmode);
    };

    return FormElementStyle;

});
N2D('FormElementSubform', ['FormElement'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param ajaxUrl
     * @param target
     * @param tab
     * @param originalValue
     * @constructor
     */
    function FormElementSubform(id, ajaxUrl, target, tab, originalValue) {
        this.id = id;

        this.ajaxUrl = ajaxUrl;

        this.element = $('#' + id);

        this.target = $('#' + target);

        this.tab = tab;

        this.originalValue = originalValue;

        this.form = this.element.closest('form').data('form');

        this.list = this.element.data('field');

        this.element.on('nextendChange', $.proxy(this.loadSubform, this));

        N2Classes.FormElement.prototype.constructor.apply(this, arguments);
    }


    FormElementSubform.prototype = Object.create(N2Classes.FormElement.prototype);
    FormElementSubform.prototype.constructor = FormElementSubform;

    FormElementSubform.prototype.loadSubform = function () {
        var value = this.element.val();
        if (value == 'disabled') {
            this.target.html('');
        } else {
            var values = [];
            if (value == this.originalValue) {
                values = this.form.values;
            }

            var data = {
                values: values,
                value: value
            };

            N2Classes.AjaxHelper.ajax({
                type: "POST",
                url: N2Classes.AjaxHelper.makeAjaxUrl(this.ajaxUrl),
                data: data,
                dataType: 'json'
            }).done($.proxy(this.load, this));
        }
    };

    FormElementSubform.prototype.load = function (response) {
        this.target.html(response.data.html);
        eval(response.data.scripts);

        nextend.tooltip.add(this.target);
    };

    return FormElementSubform;
});

N2D('FormElementSubformImage', ['FormElement'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param options
     * @constructor
     */
    function FormElementSubformImage(id, options) {

        this.element = $('#' + id);

        this.options = $('#' + options).find('.n2-subform-image-option');

        this.subform = this.element.data('field');

        this.active = this.getIndex(this.options.filter('.n2-active').get(0));

        for (var i = 0; i < this.options.length; i++) {
            this.options.eq(i).on('click', $.proxy(this.selectOption, this));
        }

        N2Classes.FormElement.prototype.constructor.apply(this, arguments);
    }

    FormElementSubformImage.prototype = Object.create(N2Classes.FormElement.prototype);
    FormElementSubformImage.prototype.constructor = FormElementSubformImage;


    FormElementSubformImage.prototype.selectOption = function (e) {
        var index = this.getIndex(e.currentTarget);
        if (index != this.active) {

            this.options.eq(index).addClass('n2-active');
            this.options.eq(this.active).removeClass('n2-active');

            this.active = index;

            var value = this.subform.list.select.find('option').eq(index).val();
            this.subform.list.insideChange(value);
        }
    };

    FormElementSubformImage.prototype.getIndex = function (option) {
        return $.inArray(option, this.options);
    };

    return FormElementSubformImage;
});
N2D('FormElementSwitcher', ['FormElement'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param values
     * @constructor
     */
    function FormElementSwitcher(id, values) {

        this.element = $('#' + id);

        this.options = this.element.parent().find('.n2-switcher-unit');

        this.active = this.options.index(this.options.filter('.n2-active'));

        this.values = values;

        for (var i = 0; i < this.options.length; i++) {
            this.options.eq(i).on('click', $.proxy(this.switch, this, i));
        }

        N2Classes.FormElement.prototype.constructor.apply(this, arguments);
    }

    FormElementSwitcher.prototype = Object.create(N2Classes.FormElement.prototype);
    FormElementSwitcher.prototype.constructor = FormElementSwitcher;


    FormElementSwitcher.prototype.switch = function (i, e) {
        this.element.val(this.values[i]);
        this.setSelected(i);

        this.triggerOutsideChange();
    };

    FormElementSwitcher.prototype.insideChange = function (value) {
        var i = $.inArray(value, this.values);

        this.element.val(this.values[i]);
        this.setSelected(i);

        this.triggerInsideChange();
    };

    FormElementSwitcher.prototype.setSelected = function (i) {
        this.options.eq(this.active).removeClass('n2-active');
        this.options.eq(i).addClass('n2-active');
        this.active = i;
    };

    return FormElementSwitcher;
});

N2D('FormElementUnits', ['FormElement'], function ($, undefined) {

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param values
     * @constructor
     */
    function FormElementUnits(id, values) {

        this.element = $('#' + id);

        this.options = this.element.parent().find('.n2-element-unit');
        this.currentUnit = this.element.parent().find('.n2-element-current-unit');

        this.values = values;

        for (var i = 0; i < this.options.length; i++) {
            this.options.eq(i).on('click', $.proxy(this.switch, this, i));
        }

        N2Classes.FormElement.prototype.constructor.apply(this, arguments);
    }

    FormElementUnits.prototype = Object.create(N2Classes.FormElement.prototype);
    FormElementUnits.prototype.constructor = FormElementUnits;


    FormElementUnits.prototype.switch = function (i, e) {
        this.element.val(this.values[i]);
        this.setSelected(i);

        this.triggerOutsideChange();
    };

    FormElementUnits.prototype.insideChange = function (value) {
        var i = $.inArray(value, this.values);

        this.element.val(this.values[i]);
        this.setSelected(i);

        this.triggerInsideChange();
    };

    FormElementUnits.prototype.setSelected = function (i) {
        this.currentUnit.html(this.options.eq(i).html());
    };

    return FormElementUnits;
});

N2D('FormElementUrl', ['FormElement'], function ($, undefined) {

    var ajaxUrl = '',
        modal = null,
        cache = {},
        callback = function (url) {
        },
        lastValue = '';

    /**
     * @memberOf N2Classes
     *
     * @param id
     * @param parameters
     * @constructor
     */
    function FormElementUrl(id, parameters) {
        this.element = $('#' + id);

        this.field = this.element.data('field');

        this.parameters = parameters;

        ajaxUrl = this.parameters.url;

        this.button = $('#' + id + '_button').on('click', $.proxy(this.open, this));

        this.element.siblings('.n2-form-element-clear')
            .on('click', $.proxy(this.clear, this));
    }

    FormElementUrl.prototype = Object.create(N2Classes.FormElement.prototype);
    FormElementUrl.prototype.constructor = FormElementUrl;

    FormElementUrl.prototype.clear = function (e) {
        e.preventDefault();
        e.stopPropagation();
        this.val('#');
    };

    FormElementUrl.prototype.val = function (value) {
        this.element.val(value);
        this.triggerOutsideChange();
    };

    FormElementUrl.prototype.open = function (e) {
        e.preventDefault();
        callback = $.proxy(this.insert, this);
        lastValue = this.element.val();
        this.getModal().show();
    };

    FormElementUrl.prototype.insert = function (url) {
        this.val(url);
    };

    FormElementUrl.prototype.getModal = function () {
        if (!modal) {
            var getLinks = function (search) {
                if (typeof cache[search] == 'undefined') {
                    cache[search] = $.ajax({
                        type: "POST",
                        url: N2Classes.AjaxHelper.makeAjaxUrl(ajaxUrl),
                        data: {
                            keyword: search
                        },
                        dataType: 'json'
                    });
                }
                return cache[search];
            };

            var parameters = this.parameters;

            var links = {
                size: [
                    600,
                    500
                ],
                title: n2_('Link'),
                back: 'zero',
                close: true,
                content: '<div class="n2-form"></div>',
                fn: {
                    show: function () {

                        this.content.find('.n2-form').append(this.createInput(n2_('Keyword'), 'n2-links-keyword', 'width:546px;'));
                        var search = $('#n2-links-keyword'),
                            heading = this.createHeading('').appendTo(this.content),
                            result = this.createResult().appendTo(this.content),
                            searchString = '';

                        search.on('keyup', $.proxy(function () {
                            searchString = search.val();
                            getLinks(searchString).done($.proxy(function (r) {
                                if (search.val() == searchString) {
                                    var links = r.data;
                                    if (searchString == '') {
                                        heading.html(n2_('No search term specified. Showing recent items.'));
                                    } else {
                                        heading.html(n2_printf(n2_('Showing items match for "%s"'), searchString));
                                    }

                                    var data = [],
                                        modal = this;
                                    for (var i = 0; i < links.length; i++) {
                                        data.push([links[i].title, links[i].info, $('<div class="n2-button n2-button-normal n2-button-xs n2-radius-s n2-button-green n2-uc n2-h5">' + n2_('Select') + '</div>')
                                            .on('click', {permalink: links[i].link}, function (e) {
                                                callback(e.data.permalink);
                                                modal.hide();
                                            })]);
                                    }
                                    result.html('');
                                    this.createTable(data, ['width:100%;', '', '']).appendTo(this.createTableWrap().appendTo(result));
                                }
                            }, this));
                        }, this))
                            .trigger('keyup').focus();

                        this.content.append('<hr style="margin: 0 -20px;"/>');
                        var external = $('<div class="n2-input-button"><input placeholder="' + n2_("External url") + '" type="text" id="external-url" name="external-url" value="" /><a href="#" class="n2-button n2-button-normal n2-button-l n2-radius-s n2-button-green n2-uc n2-h4">' + n2_("Insert") + '</a></div>')
                                .css({
                                    display: 'block',
                                    textAlign: 'center'
                                })
                                .appendTo(this.content),
                            externalInput = external.find('input').val(lastValue);

                        external.find('.n2-button').on('click', function (e) {
                            e.preventDefault();
                            callback(externalInput.val());
                            modal.hide();
                        });
                    }
                }
            };
            links.back = false;
            modal = new N2Classes.NextendModal({
                zero: links
            }, false);
        
            modal.setCustomClass('n2-url-modal');
        }
        return modal;
    };

    return FormElementUrl;
});
/*! fixto - v0.5.0 - 2016-06-16
 * http://github.com/bbarakaci/fixto/*/

N2R('$', function ($) {
    // Start Computed Style. Please do not modify this module here. Modify it from its own repo. See address below.

    /*! Computed Style - v0.1.0 - 2012-07-19
     * https://github.com/bbarakaci/computed-style
     * Copyright (c) 2012 Burak Barakaci; Licensed MIT */
    var computedStyle = (function () {
        var computedStyle = {
            getAll: function (element) {
                return document.defaultView.getComputedStyle(element);
            },
            get: function (element, name) {
                return this.getAll(element)[name];
            },
            toFloat: function (value) {
                return parseFloat(value, 10) || 0;
            },
            getFloat: function (element, name) {
                return this.toFloat(this.get(element, name));
            },
            _getAllCurrentStyle: function (element) {
                return element.currentStyle;
            }
        };

        if (document.documentElement.currentStyle) {
            computedStyle.getAll = computedStyle._getAllCurrentStyle;
        }

        return computedStyle;

    }());

    // End Computed Style. Modify whatever you want to.

    var mimicNode = (function () {
        /*
         Class Mimic Node
         Dependency : Computed Style
         Tries to mimick a dom node taking his styles, dimensions. May go to his repo if gets mature.
         */

        function MimicNode(element) {
            this.element = element;
            this.replacer = document.createElement('div');
            this.replacer.style.visibility = 'hidden';
            this.hide();
            element.parentNode.insertBefore(this.replacer, element);
        }

        MimicNode.prototype = {
            replace: function () {
                var rst = this.replacer.style;
                var styles = computedStyle.getAll(this.element);

                // rst.width = computedStyle.width(this.element) + 'px';
                // rst.height = this.element.offsetHeight + 'px';

                // Setting offsetWidth
                rst.width = this._width();
                rst.height = this._height();

                // Adopt margins
                rst.marginTop = styles.marginTop;
                rst.marginBottom = styles.marginBottom;
                rst.marginLeft = styles.marginLeft;
                rst.marginRight = styles.marginRight;

                // Adopt positioning
                rst.cssFloat = styles.cssFloat;
                rst.styleFloat = styles.styleFloat; //ie8;
                rst.position = styles.position;
                rst.top = styles.top;
                rst.right = styles.right;
                rst.bottom = styles.bottom;
                rst.left = styles.left;
                // rst.borderStyle = styles.borderStyle;

                rst.display = styles.display;

            },

            hide: function () {
                this.replacer.style.display = 'none';
            },

            _width: function () {
                return this.element.getBoundingClientRect().width + 'px';
            },

            _widthOffset: function () {
                return this.element.offsetWidth + 'px';
            },

            _height: function () {
                return this.element.getBoundingClientRect().height + 'px';
            },

            _heightOffset: function () {
                return this.element.offsetHeight + 'px';
            },

            destroy: function () {
                $(this.replacer).remove();

                // set properties to null to break references
                for (var prop in this) {
                    if (this.hasOwnProperty(prop)) {
                        this[prop] = null;
                    }
                }
            }
        };

        var bcr = document.documentElement.getBoundingClientRect();
        if (!bcr.width) {
            MimicNode.prototype._width = MimicNode.prototype._widthOffset;
            MimicNode.prototype._height = MimicNode.prototype._heightOffset;
        }

        return {
            MimicNode: MimicNode,
            computedStyle: computedStyle
        };
    }());

    // Class handles vendor prefixes
    function Prefix() {
        // Cached vendor will be stored when it is detected
        this._vendor = null;

        //this._dummy = document.createElement('div');
    }

    Prefix.prototype = {

        _vendors: {
            webkit: {cssPrefix: '-webkit-', jsPrefix: 'Webkit'},
            moz: {cssPrefix: '-moz-', jsPrefix: 'Moz'},
            ms: {cssPrefix: '-ms-', jsPrefix: 'ms'},
            opera: {cssPrefix: '-o-', jsPrefix: 'O'}
        },

        _prefixJsProperty: function (vendor, prop) {
            return vendor.jsPrefix + prop[0].toUpperCase() + prop.substr(1);
        },

        _prefixValue: function (vendor, value) {
            return vendor.cssPrefix + value;
        },

        _valueSupported: function (prop, value, dummy) {
            // IE8 will throw Illegal Argument when you attempt to set a not supported value.
            try {
                dummy.style[prop] = value;
                return dummy.style[prop] === value;
            }
            catch (er) {
                return false;
            }
        },

        /**
         * Returns true if the property is supported
         * @param {string} prop Property name
         * @returns {boolean}
         */
        propertySupported: function (prop) {
            // Supported property will return either inine style value or an empty string.
            // Undefined means property is not supported.
            return document.documentElement.style[prop] !== undefined;
        },

        /**
         * Returns prefixed property name for js usage
         * @param {string} prop Property name
         * @returns {string|null}
         */
        getJsProperty: function (prop) {
            // Try native property name first.
            if (this.propertySupported(prop)) {
                return prop;
            }

            // Prefix it if we know the vendor already
            if (this._vendor) {
                return this._prefixJsProperty(this._vendor, prop);
            }

            // We don't know the vendor, try all the possibilities
            var prefixed;
            for (var vendor in this._vendors) {
                prefixed = this._prefixJsProperty(this._vendors[vendor], prop);
                if (this.propertySupported(prefixed)) {
                    // Vendor detected. Cache it.
                    this._vendor = this._vendors[vendor];
                    return prefixed;
                }
            }

            // Nothing worked
            return null;
        },

        /**
         * Returns supported css value for css property. Could be used to check support or get prefixed value string.
         * @param {string} prop Property
         * @param {string} value Value name
         * @returns {string|null}
         */
        getCssValue: function (prop, value) {
            // Create dummy element to test value
            var dummy = document.createElement('div');

            // Get supported property name
            var jsProperty = this.getJsProperty(prop);

            // Try unprefixed value
            if (this._valueSupported(jsProperty, value, dummy)) {
                return value;
            }

            var prefixedValue;

            // If we know the vendor already try prefixed value
            if (this._vendor) {
                prefixedValue = this._prefixValue(this._vendor, value);
                if (this._valueSupported(jsProperty, prefixedValue, dummy)) {
                    return prefixedValue;
                }
            }

            // Try all vendors
            for (var vendor in this._vendors) {
                prefixedValue = this._prefixValue(this._vendors[vendor], value);
                if (this._valueSupported(jsProperty, prefixedValue, dummy)) {
                    // Vendor detected. Cache it.
                    this._vendor = this._vendors[vendor];
                    return prefixedValue;
                }
            }
            // No support for value
            return null;
        }
    };

    var prefix = new Prefix();

    // We will need this frequently. Lets have it as a global until we encapsulate properly.
    var transformJsProperty = prefix.getJsProperty('transform');

    // Will hold if browser creates a positioning context for fixed elements.
    var fixedPositioningContext;

    // Checks if browser creates a positioning context for fixed elements.
    // Transform rule will create a positioning context on browsers who follow the spec.
    // Ie for example will fix it according to documentElement
    // TODO: Other css rules also effects. perspective creates at chrome but not in firefox. transform-style preserve3d effects.
    function checkFixedPositioningContextSupport() {
        var support = false;
        var parent = document.createElement('div');
        var child = document.createElement('div');
        parent.appendChild(child);
        parent.style[transformJsProperty] = 'translate(0)';
        // Make sure there is space on top of parent
        parent.style.marginTop = '10px';
        parent.style.visibility = 'hidden';
        child.style.position = 'fixed';
        child.style.top = 0;
        document.body.appendChild(parent);
        var rect = child.getBoundingClientRect();
        // If offset top is greater than 0 meand transformed element created a positioning context.
        if (rect.top > 0) {
            support = true;
        }
        // Remove dummy content
        document.body.removeChild(parent);
        return support;
    }

    // It will return null if position sticky is not supported
    var nativeStickyValue = prefix.getCssValue('position', 'sticky');

    // It will return null if position fixed is not supported
    var fixedPositionValue = prefix.getCssValue('position', 'fixed');

    // Dirty business
    var ie = navigator.appName === 'Microsoft Internet Explorer';
    var ieversion;

    if (ie) {
        ieversion = parseFloat(navigator.appVersion.split("MSIE")[1]);
    }

    function FixTo(child, parent, options) {
        this.child = child;
        this._$child = $(child);
        this.parent = parent;
        this.options = {
            className: 'fixto-fixed',
            top: 0,
            mindViewport: false
        };
        this._setOptions(options);
    }

    FixTo.prototype = {
        // Returns the total outerHeight of the elements passed to mind option. Will return 0 if none.
        _mindtop: function () {
            var top = 0;
            if (this._$mind) {
                var el;
                var rect;
                var height;
                for (var i = 0, l = this._$mind.length; i < l; i++) {
                    el = this._$mind[i];
                    rect = el.getBoundingClientRect();
                    if (rect.height) {
                        top += rect.height;
                    }
                    else {
                        var styles = computedStyle.getAll(el);
                        top += el.offsetHeight + computedStyle.toFloat(styles.marginTop) + computedStyle.toFloat(styles.marginBottom);
                    }
                }
            }
            return top;
        },

        // Public method to stop the behaviour of this instance.
        stop: function () {
            this._stop();
            this._running = false;
        },

        // Public method starts the behaviour of this instance.
        start: function () {

            // Start only if it is not running not to attach event listeners multiple times.
            if (!this._running) {
                this._start();
                this._running = true;
            }
        },

        //Public method to destroy fixto behaviour
        destroy: function () {
            this.stop();

            this._destroy();

            // Remove jquery data from the element
            this._$child.removeData('fixto-instance');

            // set properties to null to break references
            for (var prop in this) {
                if (this.hasOwnProperty(prop)) {
                    this[prop] = null;
                }
            }
        },

        _setOptions: function (options) {
            $.extend(this.options, options);
            if (this.options.mind) {
                this._$mind = $(this.options.mind);
            }
            if (this.options.zIndex) {
                this.child.style.zIndex = this.options.zIndex;
            }
        },

        setOptions: function (options) {
            this._setOptions(options);
            this.refresh();
        },

        // Methods could be implemented by subclasses

        _stop: function () {

        },

        _start: function () {

        },

        _destroy: function () {

        },

        refresh: function () {

        }
    };

    // Class FixToContainer
    function FixToContainer(child, parent, options) {
        FixTo.call(this, child, parent, options);
        this._replacer = new mimicNode.MimicNode(child);
        this._ghostNode = this._replacer.replacer;

        this._saveStyles();

        this._saveViewportHeight();

        // Create anonymous functions and keep references to register and unregister events.
        this._proxied_onscroll = this._bind(this._onscroll, this);
        this._proxied_onresize = this._bind(this._onresize, this);

        this.start();
    }

    FixToContainer.prototype = new FixTo();

    $.extend(FixToContainer.prototype, {

        // Returns an anonymous function that will call the given function in the given context
        _bind: function (fn, context) {
            return function () {
                return fn.call(context);
            };
        },

        // at ie8 maybe only in vm window resize event fires everytime an element is resized.
        _toresize: ieversion === 8 ? document.documentElement : window,

        _onscroll: function _onscroll() {
            this._scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            this._parentBottom = (this.parent.offsetHeight + this._fullOffset('offsetTop', this.parent));

            if (this.options.mindBottomPadding !== false) {
                this._parentBottom -= computedStyle.getFloat(this.parent, 'paddingBottom');
            }

            if (!this.fixed && this._shouldFix()) {
                this._fix();
                this._adjust();
            } else {
                if (this._scrollTop > this._parentBottom || this._scrollTop < (this._fullOffset('offsetTop', this._ghostNode) - this.options.top - this._mindtop())) {
                    this._unfix();
                    return;
                }
                this._adjust();
            }
        },

        _shouldFix: function () {
            if (this._scrollTop < this._parentBottom && this._scrollTop > (this._fullOffset('offsetTop', this.child) - this.options.top - this._mindtop())) {
                if (this.options.mindViewport && !this._isViewportAvailable()) {
                    return false;
                }
                return true;
            }
        },

        _isViewportAvailable: function () {
            var childStyles = computedStyle.getAll(this.child);
            return this._viewportHeight > (this.child.offsetHeight + computedStyle.toFloat(childStyles.marginTop) + computedStyle.toFloat(childStyles.marginBottom));
        },

        _adjust: function _adjust() {
            var top = 0;
            var mindTop = this._mindtop();
            var diff = 0;
            var childStyles = computedStyle.getAll(this.child);
            var context = null;

            if (fixedPositioningContext) {
                // Get positioning context.
                context = this._getContext();
                if (context) {
                    // There is a positioning context. Top should be according to the context.
                    top = Math.abs(context.getBoundingClientRect().top);
                }
            }

            diff = (this._parentBottom - this._scrollTop) - (this.child.offsetHeight + computedStyle.toFloat(childStyles.marginBottom) + mindTop + this.options.top);

            if (diff > 0) {
                diff = 0;
            }
            if (this.fixed) {
                this.child.style.top = (diff + mindTop + top + this.options.top) - computedStyle.toFloat(childStyles.marginTop) + 'px';
            } else {
                this.child.style.top = 'auto';
            }

        },

        // Calculate cumulative offset of the element.
        // Optionally according to context
        _fullOffset: function _fullOffset(offsetName, elm, context) {
            var offset = elm[offsetName];
            var offsetParent = elm.offsetParent;

            // Add offset of the ascendent tree until we reach to the document root or to the given context
            while (offsetParent !== null && offsetParent !== context) {
                offset = offset + offsetParent[offsetName];
                offsetParent = offsetParent.offsetParent;
            }

            return offset;
        },

        // Get positioning context of the element.
        // We know that the closest parent that a transform rule applied will create a positioning context.
        _getContext: function () {
            var parent;
            var element = this.child;
            var context = null;
            var styles;

            // Climb up the treee until reaching the context
            while (!context) {
                parent = element.parentNode;
                if (parent === document.documentElement) {
                    return null;
                }

                styles = computedStyle.getAll(parent);
                // Element has a transform rule
                if (styles[transformJsProperty] !== 'none') {
                    context = parent;
                    break;
                }
                element = parent;
            }
            return context;
        },

        _fix: function _fix() {
            var isRTL = window.n2const.isRTL();
            var child = this.child;
            var childStyle = child.style;
            var childStyles = computedStyle.getAll(child);
            var xOffset;
            if (isRTL) {
                xOffset = document.body.clientWidth - child.getBoundingClientRect().right;
            } else {
                xOffset = child.getBoundingClientRect().left;
            }
            var width = childStyles.width;

            this._saveStyles();

            if (document.documentElement.currentStyle) {
                // Function for ie<9. When hasLayout is not triggered in ie7, he will report currentStyle as auto, clientWidth as 0. Thus using offsetWidth.
                // Opera also falls here
                width = (child.offsetWidth) - (computedStyle.toFloat(childStyles.paddingLeft) + computedStyle.toFloat(childStyles.paddingRight) + computedStyle.toFloat(childStyles.borderLeftWidth) + computedStyle.toFloat(childStyles.borderRightWidth)) + 'px';
            }

            // Ie still fixes the container according to the viewport.
            if (fixedPositioningContext) {
                var context = this._getContext();
                if (context) {
                    // There is a positioning context. Left should be according to the context.
                    if (isRTL) {
                        xOffset = (document.body.clientWidth - child.getBoundingClientRect().right) - (document.body.clientWidth - context.getBoundingClientRect().right);
                    } else {
                        xOffset = child.getBoundingClientRect().left - context.getBoundingClientRect().left;
                    }
                }
            }

            this._replacer.replace();

            if (isRTL) {
                childStyle.right = (xOffset - computedStyle.toFloat(childStyles.marginRight)) + 'px';
            } else {
                childStyle.left = (xOffset - computedStyle.toFloat(childStyles.marginLeft)) + 'px';
            }

            childStyle.width = width;

            childStyle.position = 'fixed';
            childStyle.top = this._mindtop() + this.options.top - computedStyle.toFloat(childStyles.marginTop) + 'px';
            this._$child.addClass(this.options.className);
            this.fixed = true;
        },

        _unfix: function _unfix() {
            var childStyle = this.child.style;
            this._replacer.hide();
            childStyle.position = this._childOriginalPosition;
            childStyle.top = this._childOriginalTop;
            childStyle.width = this._childOriginalWidth;
            if (window.n2const.isRTL()) {
                childStyle.right = this._childOriginalRight;
            } else {
                childStyle.left = this._childOriginalLeft;
            }
            this._$child.removeClass(this.options.className);
            this.fixed = false;
        },

        _saveStyles: function () {
            var childStyle = this.child.style;
            this._childOriginalPosition = childStyle.position;
            this._childOriginalTop = childStyle.top;
            this._childOriginalWidth = childStyle.width;
            if (window.n2const.isRTL()) {
                this._childOriginalRight = childStyle.right;
            } else {
                this._childOriginalLeft = childStyle.left;
            }
        },

        _onresize: function () {
            this.refresh();
        },

        _saveViewportHeight: function () {
            // ie8 doesn't support innerHeight
            this._viewportHeight = window.innerHeight || document.documentElement.clientHeight;
        },

        _stop: function () {
            // Unfix the container immediately.
            this._unfix();
            // remove event listeners
            $(window).unbind('scroll', this._proxied_onscroll);
            $(this._toresize).unbind('resize', this._proxied_onresize);
        },

        _start: function () {
            // Trigger onscroll to have the effect immediately.
            this._onscroll();

            // Attach event listeners
            $(window).bind('scroll', this._proxied_onscroll);
            $(this._toresize).bind('resize', this._proxied_onresize);
        },

        _destroy: function () {
            // Destroy mimic node instance
            this._replacer.destroy();
        },

        refresh: function () {
            this._saveViewportHeight();
            this._unfix();
            this._onscroll();
        }
    });

    function NativeSticky(child, parent, options) {
        FixTo.call(this, child, parent, options);
        this.start();
    }

    NativeSticky.prototype = new FixTo();

    $.extend(NativeSticky.prototype, {
        _start: function () {

            var childStyles = computedStyle.getAll(this.child);

            this._childOriginalPosition = childStyles.position;
            this._childOriginalTop = childStyles.top;

            this.child.style.position = nativeStickyValue;
            this.refresh();
        },

        _stop: function () {
            this.child.style.position = this._childOriginalPosition;
            this.child.style.top = this._childOriginalTop;
        },

        refresh: function () {
            this.child.style.top = this._mindtop() + this.options.top + 'px';
        }
    });


    var fixTo = function fixTo(childElement, parentElement, options) {
        if ((nativeStickyValue && !options) || (nativeStickyValue && options && options.useNativeSticky !== false)) {
            // Position sticky supported and user did not disabled the usage of it.
            return new NativeSticky(childElement, parentElement, options);
        }
        else if (fixedPositionValue) {
            // Position fixed supported

            if (fixedPositioningContext === undefined) {
                // We don't know yet if browser creates fixed positioning contexts. Check it.
                fixedPositioningContext = checkFixedPositioningContextSupport();
            }

            return new FixToContainer(childElement, parentElement, options);
        }
        else {
            return 'Neither fixed nor sticky positioning supported';
        }
    };

    /*
     No support for ie lt 8
     */

    if (ieversion < 8) {
        fixTo = function () {
            return 'not supported';
        };
    }

    // Let it be a jQuery Plugin
    $.fn.fixTo = function (targetSelector, options) {

        var $targets = $(targetSelector);

        var i = 0;
        return this.each(function () {

            // Check the data of the element.
            var instance = $(this).data('fixto-instance');

            // If the element is not bound to an instance, create the instance and save it to elements data.
            if (!instance) {
                $(this).data('fixto-instance', fixTo(this, $targets[i], options));
            }
            else {
                // If we already have the instance here, expect that targetSelector parameter will be a string
                // equal to a public methods name. Run the method on the instance without checking if
                // it exists or it is a public method or not. Cause nasty errors when necessary.
                var method = targetSelector;
                instance[method].call(instance, options);
            }
            i++;
        });
    };
});
/*
 * ----------------------------- JSTORAGE -------------------------------------
 * Simple local storage wrapper to save data on the browser side, supporting
 * all major browsers - IE6+, Firefox2+, Safari4+, Chrome4+ and Opera 10.5+
 *
 * Author: Andris Reinman, andris.reinman@gmail.com
 * Project homepage: www.jstorage.info
 *
 * Licensed under Unlicense:
 *
 * This is free and unencumbered software released into the public domain.
 *
 * Anyone is free to copy, modify, publish, use, compile, sell, or
 * distribute this software, either in source code form or as a compiled
 * binary, for any purpose, commercial or non-commercial, and by any
 * means.
 *
 * In jurisdictions that recognize copyright laws, the author or authors
 * of this software dedicate any and all copyright interest in the
 * software to the public domain. We make this dedication for the benefit
 * of the public at large and to the detriment of our heirs and
 * successors. We intend this dedication to be an overt act of
 * relinquishment in perpetuity of all present and future rights to this
 * software under copyright law.
 *
 * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * For more information, please refer to <http://unlicense.org/>
 */

N2R('$', function ($) {
    'use strict';

    var
        /* jStorage version */
        JSTORAGE_VERSION = '0.4.12',

        /* check for a JSON handling support */
        JSON = {
            parse: window.JSON && (window.JSON.parse || window.JSON.decode) ||
            String.prototype.evalJSON && function (str) {
                return String(str).evalJSON();
            } ||
            $.parseJSON ||
            $.evalJSON,
            stringify: Object.toJSON ||
            window.JSON && (window.JSON.stringify || window.JSON.encode) ||
            $.toJSON
        };

    // Break if no JSON support was found
    if (typeof JSON.parse !== 'function' || typeof JSON.stringify !== 'function') {
        throw new Error('No JSON support found, include //cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js to page');
    }

    var
        /* This is the object, that holds the cached values */
        _storage = {
            __jstorage_meta: {
                CRC32: {}
            }
        },

        /* Actual browser storage (localStorage or globalStorage['domain']) */
        _storage_service = {
            jStorage: '{}'
        },

        /* DOM element for older IE versions, holds userData behavior */
        _storage_elm = null,

        /* How much space does the storage take */
        _storage_size = 0,

        /* which backend is currently used */
        _backend = false,

        /* onchange observers */
        _observers = {},

        /* timeout to wait after onchange event */
        _observer_timeout = false,

        /* last update time */
        _observer_update = 0,

        /* pubsub observers */
        _pubsub_observers = {},

        /* skip published items older than current timestamp */
        _pubsub_last = +new Date(),

        /* Next check for TTL */
        _ttl_timeout,

        /**
         * XML encoding and decoding as XML nodes can't be JSON'ized
         * XML nodes are encoded and decoded if the node is the value to be saved
         * but not if it's as a property of another object
         * Eg. -
         *   $.jStorage.set('key', xmlNode);        // IS OK
         *   $.jStorage.set('key', {xml: xmlNode}); // NOT OK
         */
        _XMLService = {

            /**
             * Validates a XML node to be XML
             * based on jQuery.isXML function
             */
            isXML: function (elm) {
                var documentElement = (elm ? elm.ownerDocument || elm : 0).documentElement;
                return documentElement ? documentElement.nodeName !== 'HTML' : false;
            },

            /**
             * Encodes a XML node to string
             * based on http://www.mercurytide.co.uk/news/article/issues-when-working-ajax/
             */
            encode: function (xmlNode) {
                if (!this.isXML(xmlNode)) {
                    return false;
                }
                try { // Mozilla, Webkit, Opera
                    return new XMLSerializer().serializeToString(xmlNode);
                } catch (E1) {
                    try { // IE
                        return xmlNode.xml;
                    } catch (E2) {
                    }
                }
                return false;
            },

            /**
             * Decodes a XML node from string
             * loosely based on http://outwestmedia.com/jquery-plugins/xmldom/
             */
            decode: function (xmlString) {
                var dom_parser = ('DOMParser' in window && (new DOMParser()).parseFromString) ||
                    (window.ActiveXObject && function (_xmlString) {
                        var xml_doc = new ActiveXObject('Microsoft.XMLDOM');
                        xml_doc.async = 'false';
                        xml_doc.loadXML(_xmlString);
                        return xml_doc;
                    }),
                    resultXML;
                if (!dom_parser) {
                    return false;
                }
                resultXML = dom_parser.call('DOMParser' in window && (new DOMParser()) || window, xmlString, 'text/xml');
                return this.isXML(resultXML) ? resultXML : false;
            }
        };


    ////////////////////////// PRIVATE METHODS ////////////////////////

    /**
     * Initialization function. Detects if the browser supports DOM Storage
     * or userData behavior and behaves accordingly.
     */
    function _init() {
        /* Check if browser supports localStorage */
        var localStorageReallyWorks = false;
        if ('localStorage' in window) {
            try {
                window.localStorage.setItem('_tmptest', 'tmpval');
                localStorageReallyWorks = true;
                window.localStorage.removeItem('_tmptest');
            } catch (BogusQuotaExceededErrorOnIos5) {
                // Thanks be to iOS5 Private Browsing mode which throws
                // QUOTA_EXCEEDED_ERRROR DOM Exception 22.
            }
        }

        if (localStorageReallyWorks) {
            try {
                if (window.localStorage) {
                    _storage_service = window.localStorage;
                    _backend = 'localStorage';
                    _observer_update = _storage_service.jStorage_update;
                }
            } catch (E3) { /* Firefox fails when touching localStorage and cookies are disabled */
            }
        }
        /* Check if browser supports globalStorage */
        else if ('globalStorage' in window) {
            try {
                if (window.globalStorage) {
                    if (window.location.hostname == 'localhost') {
                        _storage_service = window.globalStorage['localhost.localdomain'];
                    } else {
                        _storage_service = window.globalStorage[window.location.hostname];
                    }
                    _backend = 'globalStorage';
                    _observer_update = _storage_service.jStorage_update;
                }
            } catch (E4) { /* Firefox fails when touching localStorage and cookies are disabled */
            }
        }
        /* Check if browser supports userData behavior */
        else {
            _storage_elm = document.createElement('link');
            if (_storage_elm.addBehavior) {

                /* Use a DOM element to act as userData storage */
                _storage_elm.style.behavior = 'url(#default#userData)';

                /* userData element needs to be inserted into the DOM! */
                document.getElementsByTagName('head')[0].appendChild(_storage_elm);

                try {
                    _storage_elm.load('jStorage');
                } catch (E) {
                    // try to reset cache
                    _storage_elm.setAttribute('jStorage', '{}');
                    _storage_elm.save('jStorage');
                    _storage_elm.load('jStorage');
                }

                var data = '{}';
                try {
                    data = _storage_elm.getAttribute('jStorage');
                } catch (E5) {
                }

                try {
                    _observer_update = _storage_elm.getAttribute('jStorage_update');
                } catch (E6) {
                }

                _storage_service.jStorage = data;
                _backend = 'userDataBehavior';
            } else {
                _storage_elm = null;
                return;
            }
        }

        // Load data from storage
        _load_storage();

        // remove dead keys
        _handleTTL();

        // start listening for changes
        _setupObserver();

        // initialize publish-subscribe service
        _handlePubSub();

        // handle cached navigation
        if ('addEventListener' in window) {
            window.addEventListener('pageshow', function (event) {
                if (event.persisted) {
                    _storageObserver();
                }
            }, false);
        }
    }

    /**
     * Reload data from storage when needed
     */
    function _reloadData() {
        var data = '{}';

        if (_backend == 'userDataBehavior') {
            _storage_elm.load('jStorage');

            try {
                data = _storage_elm.getAttribute('jStorage');
            } catch (E5) {
            }

            try {
                _observer_update = _storage_elm.getAttribute('jStorage_update');
            } catch (E6) {
            }

            _storage_service.jStorage = data;
        }

        _load_storage();

        // remove dead keys
        _handleTTL();

        _handlePubSub();
    }

    /**
     * Sets up a storage change observer
     */
    function _setupObserver() {
        if (_backend == 'localStorage' || _backend == 'globalStorage') {
            if ('addEventListener' in window) {
                window.addEventListener('storage', _storageObserver, false);
            } else {
                document.attachEvent('onstorage', _storageObserver);
            }
        } else if (_backend == 'userDataBehavior') {
            setInterval(_storageObserver, 1000);
        }
    }

    /**
     * Fired on any kind of data change, needs to check if anything has
     * really been changed
     */
    function _storageObserver() {
        var updateTime;
        // cumulate change notifications with timeout
        clearTimeout(_observer_timeout);
        _observer_timeout = setTimeout(function () {

            if (_backend == 'localStorage' || _backend == 'globalStorage') {
                updateTime = _storage_service.jStorage_update;
            } else if (_backend == 'userDataBehavior') {
                _storage_elm.load('jStorage');
                try {
                    updateTime = _storage_elm.getAttribute('jStorage_update');
                } catch (E5) {
                }
            }

            if (updateTime && updateTime != _observer_update) {
                _observer_update = updateTime;
                _checkUpdatedKeys();
            }

        }, 25);
    }

    /**
     * Reloads the data and checks if any keys are changed
     */
    function _checkUpdatedKeys() {
        var oldCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32)),
            newCrc32List;

        _reloadData();
        newCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32));

        var key,
            updated = [],
            removed = [];

        for (key in oldCrc32List) {
            if (oldCrc32List.hasOwnProperty(key)) {
                if (!newCrc32List[key]) {
                    removed.push(key);
                    continue;
                }
                if (oldCrc32List[key] != newCrc32List[key] && String(oldCrc32List[key]).substr(0, 2) == '2.') {
                    updated.push(key);
                }
            }
        }

        for (key in newCrc32List) {
            if (newCrc32List.hasOwnProperty(key)) {
                if (!oldCrc32List[key]) {
                    updated.push(key);
                }
            }
        }

        _fireObservers(updated, 'updated');
        _fireObservers(removed, 'deleted');
    }

    /**
     * Fires observers for updated keys
     *
     * @param {Array|String} keys Array of key names or a key
     * @param {String} action What happened with the value (updated, deleted, flushed)
     */
    function _fireObservers(keys, action) {
        keys = [].concat(keys || []);

        var i, j, len, jlen;

        if (action == 'flushed') {
            keys = [];
            for (var key in _observers) {
                if (_observers.hasOwnProperty(key)) {
                    keys.push(key);
                }
            }
            action = 'deleted';
        }
        for (i = 0, len = keys.length; i < len; i++) {
            if (_observers[keys[i]]) {
                for (j = 0, jlen = _observers[keys[i]].length; j < jlen; j++) {
                    _observers[keys[i]][j](keys[i], action);
                }
            }
            if (_observers['*']) {
                for (j = 0, jlen = _observers['*'].length; j < jlen; j++) {
                    _observers['*'][j](keys[i], action);
                }
            }
        }
    }

    /**
     * Publishes key change to listeners
     */
    function _publishChange() {
        var updateTime = (+new Date()).toString();

        if (_backend == 'localStorage' || _backend == 'globalStorage') {
            try {
                _storage_service.jStorage_update = updateTime;
            } catch (E8) {
                // safari private mode has been enabled after the jStorage initialization
                _backend = false;
            }
        } else if (_backend == 'userDataBehavior') {
            _storage_elm.setAttribute('jStorage_update', updateTime);
            _storage_elm.save('jStorage');
        }

        _storageObserver();
    }

    /**
     * Loads the data from the storage based on the supported mechanism
     */
    function _load_storage() {
        /* if jStorage string is retrieved, then decode it */
        if (_storage_service.jStorage) {
            try {
                _storage = JSON.parse(String(_storage_service.jStorage));
            } catch (E6) {
                _storage_service.jStorage = '{}';
            }
        } else {
            _storage_service.jStorage = '{}';
        }
        _storage_size = _storage_service.jStorage ? String(_storage_service.jStorage).length : 0;

        if (!_storage.__jstorage_meta) {
            _storage.__jstorage_meta = {};
        }
        if (!_storage.__jstorage_meta.CRC32) {
            _storage.__jstorage_meta.CRC32 = {};
        }
    }

    /**
     * This functions provides the 'save' mechanism to store the jStorage object
     */
    function _save() {
        _dropOldEvents(); // remove expired events
        try {
            _storage_service.jStorage = JSON.stringify(_storage);
            // If userData is used as the storage engine, additional
            if (_storage_elm) {
                _storage_elm.setAttribute('jStorage', _storage_service.jStorage);
                _storage_elm.save('jStorage');
            }
            _storage_size = _storage_service.jStorage ? String(_storage_service.jStorage).length : 0;
        } catch (E7) { /* probably cache is full, nothing is saved this way*/
        }
    }

    /**
     * Function checks if a key is set and is string or numberic
     *
     * @param {String} key Key name
     */
    function _checkKey(key) {
        if (typeof key != 'string' && typeof key != 'number') {
            throw new TypeError('Key name must be string or numeric');
        }
        if (key == '__jstorage_meta') {
            throw new TypeError('Reserved key name');
        }
        return true;
    }

    /**
     * Removes expired keys
     */
    function _handleTTL() {
        var curtime, i, TTL, CRC32, nextExpire = Infinity,
            changed = false,
            deleted = [];

        clearTimeout(_ttl_timeout);

        if (!_storage.__jstorage_meta || typeof _storage.__jstorage_meta.TTL != 'object') {
            // nothing to do here
            return;
        }

        curtime = +new Date();
        TTL = _storage.__jstorage_meta.TTL;

        CRC32 = _storage.__jstorage_meta.CRC32;
        for (i in TTL) {
            if (TTL.hasOwnProperty(i)) {
                if (TTL[i] <= curtime) {
                    delete TTL[i];
                    delete CRC32[i];
                    delete _storage[i];
                    changed = true;
                    deleted.push(i);
                } else if (TTL[i] < nextExpire) {
                    nextExpire = TTL[i];
                }
            }
        }

        // set next check
        if (nextExpire != Infinity) {
            _ttl_timeout = setTimeout(_handleTTL, Math.min(nextExpire - curtime, 0x7FFFFFFF));
        }

        // save changes
        if (changed) {
            _save();
            _publishChange();
            _fireObservers(deleted, 'deleted');
        }
    }

    /**
     * Checks if there's any events on hold to be fired to listeners
     */
    function _handlePubSub() {
        var i, len;
        if (!_storage.__jstorage_meta.PubSub) {
            return;
        }
        var pubelm,
            _pubsubCurrent = _pubsub_last,
            needFired = [];

        for (i = len = _storage.__jstorage_meta.PubSub.length - 1; i >= 0; i--) {
            pubelm = _storage.__jstorage_meta.PubSub[i];
            if (pubelm[0] > _pubsub_last) {
                _pubsubCurrent = pubelm[0];
                needFired.unshift(pubelm);
            }
        }

        for (i = needFired.length - 1; i >= 0; i--) {
            _fireSubscribers(needFired[i][1], needFired[i][2]);
        }

        _pubsub_last = _pubsubCurrent;
    }

    /**
     * Fires all subscriber listeners for a pubsub channel
     *
     * @param {String} channel Channel name
     * @param {Mixed} payload Payload data to deliver
     */
    function _fireSubscribers(channel, payload) {
        if (_pubsub_observers[channel]) {
            for (var i = 0, len = _pubsub_observers[channel].length; i < len; i++) {
                // send immutable data that can't be modified by listeners
                try {
                    _pubsub_observers[channel][i](channel, JSON.parse(JSON.stringify(payload)));
                } catch (E) {
                }
            }
        }
    }

    /**
     * Remove old events from the publish stream (at least 2sec old)
     */
    function _dropOldEvents() {
        if (!_storage.__jstorage_meta.PubSub) {
            return;
        }

        var retire = +new Date() - 2000;

        for (var i = 0, len = _storage.__jstorage_meta.PubSub.length; i < len; i++) {
            if (_storage.__jstorage_meta.PubSub[i][0] <= retire) {
                // deleteCount is needed for IE6
                _storage.__jstorage_meta.PubSub.splice(i, _storage.__jstorage_meta.PubSub.length - i);
                break;
            }
        }

        if (!_storage.__jstorage_meta.PubSub.length) {
            delete _storage.__jstorage_meta.PubSub;
        }

    }

    /**
     * Publish payload to a channel
     *
     * @param {String} channel Channel name
     * @param {Mixed} payload Payload to send to the subscribers
     */
    function _publish(channel, payload) {
        if (!_storage.__jstorage_meta) {
            _storage.__jstorage_meta = {};
        }
        if (!_storage.__jstorage_meta.PubSub) {
            _storage.__jstorage_meta.PubSub = [];
        }

        _storage.__jstorage_meta.PubSub.unshift([+new Date(), channel, payload]);

        _save();
        _publishChange();
    }


    /**
     * JS Implementation of MurmurHash2
     *
     *  SOURCE: https://github.com/garycourt/murmurhash-js (MIT licensed)
     *
     * @author <a href='mailto:gary.court@gmail.com'>Gary Court</a>
     * @see http://github.com/garycourt/murmurhash-js
     * @author <a href='mailto:aappleby@gmail.com'>Austin Appleby</a>
     * @see http://sites.google.com/site/murmurhash/
     *
     * @param {string} str ASCII only
     * @param {number} seed Positive integer only
     * @return {number} 32-bit positive integer hash
     */

    function murmurhash2_32_gc(str, seed) {
        var
            l = str.length,
            h = seed ^ l,
            i = 0,
            k;

        while (l >= 4) {
            k =
                ((str.charCodeAt(i) & 0xff)) |
                ((str.charCodeAt(++i) & 0xff) << 8) |
                ((str.charCodeAt(++i) & 0xff) << 16) |
                ((str.charCodeAt(++i) & 0xff) << 24);

            k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
            k ^= k >>> 24;
            k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));

            h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k;

            l -= 4;
            ++i;
        }

        switch (l) {
            case 3:
                h ^= (str.charCodeAt(i + 2) & 0xff) << 16;
            /* falls through */
            case 2:
                h ^= (str.charCodeAt(i + 1) & 0xff) << 8;
            /* falls through */
            case 1:
                h ^= (str.charCodeAt(i) & 0xff);
                h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
        }

        h ^= h >>> 13;
        h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
        h ^= h >>> 15;

        return h >>> 0;
    }

    ////////////////////////// PUBLIC INTERFACE /////////////////////////

    $.jStorage = {
        /* Version number */
        version: JSTORAGE_VERSION,

        /**
         * Sets a key's value.
         *
         * @param {String} key Key to set. If this value is not set or not
         *              a string an exception is raised.
         * @param {Mixed} value Value to set. This can be any value that is JSON
         *              compatible (Numbers, Strings, Objects etc.).
         * @param {Object} [options] - possible options to use
         * @param {Number} [options.TTL] - optional TTL value, in milliseconds
         * @return {Mixed} the used value
         */
        set: function (key, value, options) {
            _checkKey(key);

            options = options || {};

            // undefined values are deleted automatically
            if (typeof value == 'undefined') {
                this.deleteKey(key);
                return value;
            }

            if (_XMLService.isXML(value)) {
                value = {
                    _is_xml: true,
                    xml: _XMLService.encode(value)
                };
            } else if (typeof value == 'function') {
                return undefined; // functions can't be saved!
            } else if (value && typeof value == 'object') {
                // clone the object before saving to _storage tree
                value = JSON.parse(JSON.stringify(value));
            }

            _storage[key] = value;

            _storage.__jstorage_meta.CRC32[key] = '2.' + murmurhash2_32_gc(JSON.stringify(value), 0x9747b28c);

            this.setTTL(key, options.TTL || 0); // also handles saving and _publishChange

            _fireObservers(key, 'updated');
            return value;
        },

        /**
         * Looks up a key in cache
         *
         * @param {String} key - Key to look up.
         * @param {mixed} def - Default value to return, if key didn't exist.
         * @return {Mixed} the key value, default value or null
         */
        get: function (key, def) {
            _checkKey(key);
            if (key in _storage) {
                if (_storage[key] && typeof _storage[key] == 'object' && _storage[key]._is_xml) {
                    return _XMLService.decode(_storage[key].xml);
                } else {
                    return _storage[key];
                }
            }
            return typeof(def) == 'undefined' ? null : def;
        },

        /**
         * Deletes a key from cache.
         *
         * @param {String} key - Key to delete.
         * @return {Boolean} true if key existed or false if it didn't
         */
        deleteKey: function (key) {
            _checkKey(key);
            if (key in _storage) {
                delete _storage[key];
                // remove from TTL list
                if (typeof _storage.__jstorage_meta.TTL == 'object' &&
                    key in _storage.__jstorage_meta.TTL) {
                    delete _storage.__jstorage_meta.TTL[key];
                }

                delete _storage.__jstorage_meta.CRC32[key];

                _save();
                _publishChange();
                _fireObservers(key, 'deleted');
                return true;
            }
            return false;
        },

        /**
         * Sets a TTL for a key, or remove it if ttl value is 0 or below
         *
         * @param {String} key - key to set the TTL for
         * @param {Number} ttl - TTL timeout in milliseconds
         * @return {Boolean} true if key existed or false if it didn't
         */
        setTTL: function (key, ttl) {
            var curtime = +new Date();
            _checkKey(key);
            ttl = Number(ttl) || 0;
            if (key in _storage) {

                if (!_storage.__jstorage_meta.TTL) {
                    _storage.__jstorage_meta.TTL = {};
                }

                // Set TTL value for the key
                if (ttl > 0) {
                    _storage.__jstorage_meta.TTL[key] = curtime + ttl;
                } else {
                    delete _storage.__jstorage_meta.TTL[key];
                }

                _save();

                _handleTTL();

                _publishChange();
                return true;
            }
            return false;
        },

        /**
         * Gets remaining TTL (in milliseconds) for a key or 0 when no TTL has been set
         *
         * @param {String} key Key to check
         * @return {Number} Remaining TTL in milliseconds
         */
        getTTL: function (key) {
            var curtime = +new Date(),
                ttl;
            _checkKey(key);
            if (key in _storage && _storage.__jstorage_meta.TTL && _storage.__jstorage_meta.TTL[key]) {
                ttl = _storage.__jstorage_meta.TTL[key] - curtime;
                return ttl || 0;
            }
            return 0;
        },

        /**
         * Deletes everything in cache.
         *
         * @return {Boolean} Always true
         */
        flush: function () {
            _storage = {
                __jstorage_meta: {
                    CRC32: {}
                }
            };
            _save();
            _publishChange();
            _fireObservers(null, 'flushed');
            return true;
        },

        /**
         * Returns a read-only copy of _storage
         *
         * @return {Object} Read-only copy of _storage
         */
        storageObj: function () {
            function F() {
            }

            F.prototype = _storage;
            return new F();
        },

        /**
         * Returns an index of all used keys as an array
         * ['key1', 'key2',..'keyN']
         *
         * @return {Array} Used keys
         */
        index: function () {
            var index = [],
                i;
            for (i in _storage) {
                if (_storage.hasOwnProperty(i) && i != '__jstorage_meta') {
                    index.push(i);
                }
            }
            return index;
        },

        /**
         * How much space in bytes does the storage take?
         *
         * @return {Number} Storage size in chars (not the same as in bytes,
         *                  since some chars may take several bytes)
         */
        storageSize: function () {
            return _storage_size;
        },

        /**
         * Which backend is currently in use?
         *
         * @return {String} Backend name
         */
        currentBackend: function () {
            return _backend;
        },

        /**
         * Test if storage is available
         *
         * @return {Boolean} True if storage can be used
         */
        storageAvailable: function () {
            return !!_backend;
        },

        /**
         * Register change listeners
         *
         * @param {String} key Key name
         * @param {Function} callback Function to run when the key changes
         */
        listenKeyChange: function (key, callback) {
            _checkKey(key);
            if (!_observers[key]) {
                _observers[key] = [];
            }
            _observers[key].push(callback);
        },

        /**
         * Remove change listeners
         *
         * @param {String} key Key name to unregister listeners against
         * @param {Function} [callback] If set, unregister the callback, if not - unregister all
         */
        stopListening: function (key, callback) {
            _checkKey(key);

            if (!_observers[key]) {
                return;
            }

            if (!callback) {
                delete _observers[key];
                return;
            }

            for (var i = _observers[key].length - 1; i >= 0; i--) {
                if (_observers[key][i] == callback) {
                    _observers[key].splice(i, 1);
                }
            }
        },

        /**
         * Subscribe to a Publish/Subscribe event stream
         *
         * @param {String} channel Channel name
         * @param {Function} callback Function to run when the something is published to the channel
         */
        subscribe: function (channel, callback) {
            channel = (channel || '').toString();
            if (!channel) {
                throw new TypeError('Channel not defined');
            }
            if (!_pubsub_observers[channel]) {
                _pubsub_observers[channel] = [];
            }
            _pubsub_observers[channel].push(callback);
        },

        /**
         * Publish data to an event stream
         *
         * @param {String} channel Channel name
         * @param {Mixed} payload Payload to deliver
         */
        publish: function (channel, payload) {
            channel = (channel || '').toString();
            if (!channel) {
                throw new TypeError('Channel not defined');
            }

            _publish(channel, payload);
        },

        /**
         * Reloads the data from browser storage
         */
        reInit: function () {
            _reloadData();
        },

        /**
         * Removes reference from global objects and saves it as jStorage
         *
         * @param {Boolean} option if needed to save object as simple 'jStorage' in windows context
         */
        noConflict: function (saveInGlobal) {
            delete window.$.jStorage;

            if (saveInGlobal) {
                window.jStorage = this;
            }

            return this;
        }
    };

    // Initialize jStorage
    _init();

});
/**
 * @preserve jQuery DateTimePicker plugin v2.4.1
 * @homepage http://xdsoft.net/jqplugins/datetimepicker/
 * (c) 2014, Chupurnov Valeriy.
 */
N2R('$', function ($) {
    'use strict';

    var default_options = {
        i18n: {
            ar: { // Arabic
                months: [
                    "كانون الثاني", "شباط", "آذار", "نيسان", "مايو", "حزيران", "تموز", "آب", "أيلول", "تشرين الأول", "تشرين الثاني", "كانون الأول"
                ],
                dayOfWeek: [
                    "ن", "ث", "ع", "خ", "ج", "س", "ح"
                ]
            },
            ro: { // Romanian
                months: [
                    "ianuarie", "februarie", "martie", "aprilie", "mai", "iunie", "iulie", "august", "septembrie", "octombrie", "noiembrie", "decembrie"
                ],
                dayOfWeek: [
                    "l", "ma", "mi", "j", "v", "s", "d"
                ]
            },
            id: { // Indonesian
                months: [
                    "Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember"
                ],
                dayOfWeek: [
                    "Sen", "Sel", "Rab", "Kam", "Jum", "Sab", "Min"
                ]
            },
            bg: { // Bulgarian
                months: [
                    "Януари", "Февруари", "Март", "Април", "Май", "Юни", "Юли", "Август", "Септември", "Октомври", "Ноември", "Декември"
                ],
                dayOfWeek: [
                    "Нд", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб"
                ]
            },
            fa: { // Persian/Farsi
                months: [
                    'فروردین', 'اردیبهشت', 'خرداد', 'تیر', 'مرداد', 'شهریور', 'مهر', 'آبان', 'آذر', 'دی', 'بهمن', 'اسفند'
                ],
                dayOfWeek: [
                    'یکشنبه', 'دوشنبه', 'سه شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه', 'شنبه'
                ]
            },
            ru: { // Russian
                months: [
                    'Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'
                ],
                dayOfWeek: [
                    "Вск", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб"
                ]
            },
            uk: { // Ukrainian
                months: [
                    'Січень', 'Лютий', 'Березень', 'Квітень', 'Травень', 'Червень', 'Липень', 'Серпень', 'Вересень', 'Жовтень', 'Листопад', 'Грудень'
                ],
                dayOfWeek: [
                    "Ндл", "Пнд", "Втр", "Срд", "Чтв", "Птн", "Сбт"
                ]
            },
            en: { // English
                months: [
                    "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
                ],
                dayOfWeek: [
                    "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
                ]
            },
            el: { // Ελληνικά
                months: [
                    "Ιανουάριος", "Φεβρουάριος", "Μάρτιος", "Απρίλιος", "Μάιος", "Ιούνιος", "Ιούλιος", "Αύγουστος", "Σεπτέμβριος", "Οκτώβριος", "Νοέμβριος", "Δεκέμβριος"
                ],
                dayOfWeek: [
                    "Κυρ", "Δευ", "Τρι", "Τετ", "Πεμ", "Παρ", "Σαβ"
                ]
            },
            de: { // German
                months: [
                    'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'
                ],
                dayOfWeek: [
                    "So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"
                ]
            },
            nl: { // Dutch
                months: [
                    "januari", "februari", "maart", "april", "mei", "juni", "juli", "augustus", "september", "oktober", "november", "december"
                ],
                dayOfWeek: [
                    "zo", "ma", "di", "wo", "do", "vr", "za"
                ]
            },
            tr: { // Turkish
                months: [
                    "Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran", "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık"
                ],
                dayOfWeek: [
                    "Paz", "Pts", "Sal", "Çar", "Per", "Cum", "Cts"
                ]
            },
            fr: { //French
                months: [
                    "Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"
                ],
                dayOfWeek: [
                    "Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"
                ]
            },
            es: { // Spanish
                months: [
                    "Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"
                ],
                dayOfWeek: [
                    "Dom", "Lun", "Mar", "Mié", "Jue", "Vie", "Sáb"
                ]
            },
            th: { // Thai
                months: [
                    'มกราคม', 'กุมภาพันธ์', 'มีนาคม', 'เมษายน', 'พฤษภาคม', 'มิถุนายน', 'กรกฎาคม', 'สิงหาคม', 'กันยายน', 'ตุลาคม', 'พฤศจิกายน', 'ธันวาคม'
                ],
                dayOfWeek: [
                    'อา.', 'จ.', 'อ.', 'พ.', 'พฤ.', 'ศ.', 'ส.'
                ]
            },
            pl: { // Polish
                months: [
                    "styczeń", "luty", "marzec", "kwiecień", "maj", "czerwiec", "lipiec", "sierpień", "wrzesień", "październik", "listopad", "grudzień"
                ],
                dayOfWeek: [
                    "nd", "pn", "wt", "śr", "cz", "pt", "sb"
                ]
            },
            pt: { // Portuguese
                months: [
                    "Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"
                ],
                dayOfWeek: [
                    "Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sab"
                ]
            },
            ch: { // Simplified Chinese
                months: [
                    "一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"
                ],
                dayOfWeek: [
                    "日", "一", "二", "三", "四", "五", "六"
                ]
            },
            se: { // Swedish
                months: [
                    "Januari", "Februari", "Mars", "April", "Maj", "Juni", "Juli", "Augusti", "September", "Oktober", "November", "December"
                ],
                dayOfWeek: [
                    "Sön", "Mån", "Tis", "Ons", "Tor", "Fre", "Lör"
                ]
            },
            kr: { // Korean
                months: [
                    "1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"
                ],
                dayOfWeek: [
                    "일", "월", "화", "수", "목", "금", "토"
                ]
            },
            it: { // Italian
                months: [
                    "Gennaio", "Febbraio", "Marzo", "Aprile", "Maggio", "Giugno", "Luglio", "Agosto", "Settembre", "Ottobre", "Novembre", "Dicembre"
                ],
                dayOfWeek: [
                    "Dom", "Lun", "Mar", "Mer", "Gio", "Ven", "Sab"
                ]
            },
            da: { // Dansk
                months: [
                    "January", "Februar", "Marts", "April", "Maj", "Juni", "July", "August", "September", "Oktober", "November", "December"
                ],
                dayOfWeek: [
                    "Søn", "Man", "Tir", "Ons", "Tor", "Fre", "Lør"
                ]
            },
            no: { // Norwegian
                months: [
                    "Januar", "Februar", "Mars", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Desember"
                ],
                dayOfWeek: [
                    "Søn", "Man", "Tir", "Ons", "Tor", "Fre", "Lør"
                ]
            },
            ja: { // Japanese
                months: [
                    "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"
                ],
                dayOfWeek: [
                    "日", "月", "火", "水", "木", "金", "土"
                ]
            },
            vi: { // Vietnamese
                months: [
                    "Tháng 1", "Tháng 2", "Tháng 3", "Tháng 4", "Tháng 5", "Tháng 6", "Tháng 7", "Tháng 8", "Tháng 9", "Tháng 10", "Tháng 11", "Tháng 12"
                ],
                dayOfWeek: [
                    "CN", "T2", "T3", "T4", "T5", "T6", "T7"
                ]
            },
            sl: { // Slovenščina
                months: [
                    "Januar", "Februar", "Marec", "April", "Maj", "Junij", "Julij", "Avgust", "September", "Oktober", "November", "December"
                ],
                dayOfWeek: [
                    "Ned", "Pon", "Tor", "Sre", "Čet", "Pet", "Sob"
                ]
            },
            cs: { // Čeština
                months: [
                    "Leden", "Únor", "Březen", "Duben", "Květen", "Červen", "Červenec", "Srpen", "Září", "Říjen", "Listopad", "Prosinec"
                ],
                dayOfWeek: [
                    "Ne", "Po", "Út", "St", "Čt", "Pá", "So"
                ]
            },
            hu: { // Hungarian
                months: [
                    "Január", "Február", "Március", "Április", "Május", "Június", "Július", "Augusztus", "Szeptember", "Október", "November", "December"
                ],
                dayOfWeek: [
                    "Va", "Hé", "Ke", "Sze", "Cs", "Pé", "Szo"
                ]
            },
            az: { //Azerbaijanian (Azeri)
                months: [
                    "Yanvar", "Fevral", "Mart", "Aprel", "May", "Iyun", "Iyul", "Avqust", "Sentyabr", "Oktyabr", "Noyabr", "Dekabr"
                ],
                dayOfWeek: [
                    "B", "Be", "Ça", "Ç", "Ca", "C", "Ş"
                ]
            },
            bs: { //Bosanski
                months: [
                    "Januar", "Februar", "Mart", "April", "Maj", "Jun", "Jul", "Avgust", "Septembar", "Oktobar", "Novembar", "Decembar"
                ],
                dayOfWeek: [
                    "Ned", "Pon", "Uto", "Sri", "Čet", "Pet", "Sub"
                ]
            },
            ca: { //Català
                months: [
                    "Gener", "Febrer", "Març", "Abril", "Maig", "Juny", "Juliol", "Agost", "Setembre", "Octubre", "Novembre", "Desembre"
                ],
                dayOfWeek: [
                    "Dg", "Dl", "Dt", "Dc", "Dj", "Dv", "Ds"
                ]
            },
            'en-GB': { //English (British)
                months: [
                    "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
                ],
                dayOfWeek: [
                    "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
                ]
            },
            et: { //"Eesti"
                months: [
                    "Jaanuar", "Veebruar", "Märts", "Aprill", "Mai", "Juuni", "Juuli", "August", "September", "Oktoober", "November", "Detsember"
                ],
                dayOfWeek: [
                    "P", "E", "T", "K", "N", "R", "L"
                ]
            },
            eu: { //Euskara
                months: [
                    "Urtarrila", "Otsaila", "Martxoa", "Apirila", "Maiatza", "Ekaina", "Uztaila", "Abuztua", "Iraila", "Urria", "Azaroa", "Abendua"
                ],
                dayOfWeek: [
                    "Ig.", "Al.", "Ar.", "Az.", "Og.", "Or.", "La."
                ]
            },
            fi: { //Finnish (Suomi)
                months: [
                    "Tammikuu", "Helmikuu", "Maaliskuu", "Huhtikuu", "Toukokuu", "Kesäkuu", "Heinäkuu", "Elokuu", "Syyskuu", "Lokakuu", "Marraskuu", "Joulukuu"
                ],
                dayOfWeek: [
                    "Su", "Ma", "Ti", "Ke", "To", "Pe", "La"
                ]
            },
            gl: { //Galego
                months: [
                    "Xan", "Feb", "Maz", "Abr", "Mai", "Xun", "Xul", "Ago", "Set", "Out", "Nov", "Dec"
                ],
                dayOfWeek: [
                    "Dom", "Lun", "Mar", "Mer", "Xov", "Ven", "Sab"
                ]
            },
            hr: { //Hrvatski
                months: [
                    "Siječanj", "Veljača", "Ožujak", "Travanj", "Svibanj", "Lipanj", "Srpanj", "Kolovoz", "Rujan", "Listopad", "Studeni", "Prosinac"
                ],
                dayOfWeek: [
                    "Ned", "Pon", "Uto", "Sri", "Čet", "Pet", "Sub"
                ]
            },
            ko: { //Korean (한국어)
                months: [
                    "1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"
                ],
                dayOfWeek: [
                    "일", "월", "화", "수", "목", "금", "토"
                ]
            },
            lt: { //Lithuanian (lietuvių)
                months: [
                    "Sausio", "Vasario", "Kovo", "Balandžio", "Gegužės", "Birželio", "Liepos", "Rugpjūčio", "Rugsėjo", "Spalio", "Lapkričio", "Gruodžio"
                ],
                dayOfWeek: [
                    "Sek", "Pir", "Ant", "Tre", "Ket", "Pen", "Šeš"
                ]
            },
            lv: { //Latvian (Latviešu)
                months: [
                    "Janvāris", "Februāris", "Marts", "Aprīlis ", "Maijs", "Jūnijs", "Jūlijs", "Augusts", "Septembris", "Oktobris", "Novembris", "Decembris"
                ],
                dayOfWeek: [
                    "Sv", "Pr", "Ot", "Tr", "Ct", "Pk", "St"
                ]
            },
            mk: { //Macedonian (Македонски)
                months: [
                    "јануари", "февруари", "март", "април", "мај", "јуни", "јули", "август", "септември", "октомври", "ноември", "декември"
                ],
                dayOfWeek: [
                    "нед", "пон", "вто", "сре", "чет", "пет", "саб"
                ]
            },
            mn: { //Mongolian (Монгол)
                months: [
                    "1-р сар", "2-р сар", "3-р сар", "4-р сар", "5-р сар", "6-р сар", "7-р сар", "8-р сар", "9-р сар", "10-р сар", "11-р сар", "12-р сар"
                ],
                dayOfWeek: [
                    "Дав", "Мяг", "Лха", "Пүр", "Бсн", "Бям", "Ням"
                ]
            },
            'pt-BR': { //Português(Brasil)
                months: [
                    "Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"
                ],
                dayOfWeek: [
                    "Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"
                ]
            },
            sk: { //Slovenčina
                months: [
                    "Január", "Február", "Marec", "Apríl", "Máj", "Jún", "Júl", "August", "September", "Október", "November", "December"
                ],
                dayOfWeek: [
                    "Ne", "Po", "Ut", "St", "Št", "Pi", "So"
                ]
            },
            sq: { //Albanian (Shqip)
                months: [
                    "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
                ],
                dayOfWeek: [
                    "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
                ]
            },
            'sr-YU': { //Serbian (Srpski)
                months: [
                    "Januar", "Februar", "Mart", "April", "Maj", "Jun", "Jul", "Avgust", "Septembar", "Oktobar", "Novembar", "Decembar"
                ],
                dayOfWeek: [
                    "Ned", "Pon", "Uto", "Sre", "čet", "Pet", "Sub"
                ]
            },
            sr: { //Serbian Cyrillic (Српски)
                months: [
                    "јануар", "фебруар", "март", "април", "мај", "јун", "јул", "август", "септембар", "октобар", "новембар", "децембар"
                ],
                dayOfWeek: [
                    "нед", "пон", "уто", "сре", "чет", "пет", "суб"
                ]
            },
            sv: { //Svenska
                months: [
                    "Januari", "Februari", "Mars", "April", "Maj", "Juni", "Juli", "Augusti", "September", "Oktober", "November", "December"
                ],
                dayOfWeek: [
                    "Sön", "Mån", "Tis", "Ons", "Tor", "Fre", "Lör"
                ]
            },
            'zh-TW': { //Traditional Chinese (繁體中文)
                months: [
                    "一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"
                ],
                dayOfWeek: [
                    "日", "一", "二", "三", "四", "五", "六"
                ]
            },
            zh: { //Simplified Chinese (简体中文)
                months: [
                    "一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"
                ],
                dayOfWeek: [
                    "日", "一", "二", "三", "四", "五", "六"
                ]
            },
            he: { //Hebrew (עברית)
                months: [
                    'ינואר', 'פברואר', 'מרץ', 'אפריל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ספטמבר', 'אוקטובר', 'נובמבר', 'דצמבר'
                ],
                dayOfWeek: [
                    'א\'', 'ב\'', 'ג\'', 'ד\'', 'ה\'', 'ו\'', 'שבת'
                ]
            }
        },
        value: '',
        lang: 'en',

        format: 'Y/m/d H:i',
        formatTime: 'H:i',
        formatDate: 'Y/m/d',

        startDate: false, // new Date(), '1986/12/08', '-1970/01/05','-1970/01/05',
        step: 60,
        monthChangeSpinner: true,

        closeOnDateSelect: false,
        closeOnWithoutClick: true,
        closeOnInputClick: true,

        timepicker: true,
        datepicker: true,
        weeks: false,

        defaultTime: false,	// use formatTime format (ex. '10:00' for formatTime:	'H:i')
        defaultDate: false,	// use formatDate format (ex new Date() or '1986/12/08' or '-1970/01/05' or '-1970/01/05')

        minDate: false,
        maxDate: false,
        minTime: false,
        maxTime: false,

        allowTimes: [],
        opened: false,
        initTime: true,
        inline: false,
        theme: '',

        onSelectDate: function () {
        },
        onSelectTime: function () {
        },
        onChangeMonth: function () {
        },
        onChangeYear: function () {
        },
        onChangeDateTime: function () {
        },
        onShow: function () {
        },
        onClose: function () {
        },
        onGenerate: function () {
        },

        withoutCopyright: true,
        inverseButton: false,
        hours12: false,
        next: 'xdsoft_next',
        prev: 'xdsoft_prev',
        dayOfWeekStart: 0,
        parentID: 'body',
        timeHeightInTimePicker: 25,
        timepickerScrollbar: true,
        todayButton: true,
        defaultSelect: true,

        scrollMonth: true,
        scrollTime: true,
        scrollInput: true,

        lazyInit: false,
        mask: false,
        validateOnBlur: true,
        allowBlank: true,
        yearStart: 1950,
        yearEnd: 2050,
        style: '',
        id: '',
        fixed: false,
        roundTime: 'round', // ceil, floor
        className: '',
        weekends: [],
        disabledDates: [],
        yearOffset: 0,
        beforeShowDay: null,

        enterLikeTab: true
    };
    // fix for ie8
    if (!Array.prototype.indexOf) {
        Array.prototype.indexOf = function (obj, start) {
            var i, j;
            for (i = (start || 0), j = this.length; i < j; i += 1) {
                if (this[i] === obj) {
                    return i;
                }
            }
            return -1;
        };
    }
    Date.prototype.countDaysInMonth = function () {
        return new Date(this.getFullYear(), this.getMonth() + 1, 0).getDate();
    };
    $.fn.xdsoftScroller = function (percent) {
        return this.each(function () {
            var timeboxparent = $(this),
                pointerEventToXY = function (e) {
                    var out = {x: 0, y: 0},
                        touch;
                    if (e.type === 'touchstart' || e.type === 'touchmove' || e.type === 'touchend' || e.type === 'touchcancel') {
                        touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
                        out.x = touch.clientX;
                        out.y = touch.clientY;
                    } else if (e.type === 'mousedown' || e.type === 'mouseup' || e.type === 'mousemove' || e.type === 'mouseover' || e.type === 'mouseout' || e.type === 'mouseenter' || e.type === 'mouseleave') {
                        out.x = e.clientX;
                        out.y = e.clientY;
                    }
                    return out;
                },
                move = 0,
                timebox,
                parentHeight,
                height,
                scrollbar,
                scroller,
                maximumOffset = 100,
                start = false,
                startY = 0,
                startTop = 0,
                h1 = 0,
                touchStart = false,
                startTopScroll = 0,
                calcOffset = function () {
                };
            if (percent === 'hide') {
                timeboxparent.find('.xdsoft_scrollbar').hide();
                return;
            }
            if (!$(this).hasClass('xdsoft_scroller_box')) {
                timebox = timeboxparent.children().eq(0);
                parentHeight = timeboxparent[0].clientHeight;
                height = timebox[0].offsetHeight;
                scrollbar = $('<div class="xdsoft_scrollbar"></div>');
                scroller = $('<div class="xdsoft_scroller"></div>');
                scrollbar.append(scroller);

                timeboxparent.addClass('xdsoft_scroller_box').append(scrollbar);
                calcOffset = function calcOffset(event) {
                    var offset = pointerEventToXY(event).y - startY + startTopScroll;
                    if (offset < 0) {
                        offset = 0;
                    }
                    if (offset + scroller[0].offsetHeight > h1) {
                        offset = h1 - scroller[0].offsetHeight;
                    }
                    timeboxparent.trigger('scroll_element.xdsoft_scroller', [maximumOffset ? offset / maximumOffset : 0]);
                };

                scroller
                    .on('touchstart.xdsoft_scroller mousedown.xdsoft_scroller', function (event) {
                        if (!parentHeight) {
                            timeboxparent.trigger('resize_scroll.xdsoft_scroller', [percent]);
                        }

                        startY = pointerEventToXY(event).y;
                        startTopScroll = parseInt(scroller.css('margin-top'), 10);
                        h1 = scrollbar[0].offsetHeight;

                        if (event.type === 'mousedown') {
                            if (document) {
                                $(document.body).addClass('xdsoft_noselect');
                            }
                            $([document.body, window]).on('mouseup.xdsoft_scroller', function arguments_callee() {
                                $([document.body, window]).off('mouseup.xdsoft_scroller', arguments_callee)
                                    .off('mousemove.xdsoft_scroller', calcOffset)
                                    .removeClass('xdsoft_noselect');
                            });
                            $(document.body).on('mousemove.xdsoft_scroller', calcOffset);
                        } else {
                            touchStart = true;
                            event.stopPropagation();
                            event.preventDefault();
                        }
                    })
                    .on('touchmove', function (event) {
                        if (touchStart) {
                            event.preventDefault();
                            calcOffset(event);
                        }
                    })
                    .on('touchend touchcancel', function (event) {
                        touchStart = false;
                        startTopScroll = 0;
                    });

                timeboxparent
                    .on('scroll_element.xdsoft_scroller', function (event, percentage) {
                        if (!parentHeight) {
                            timeboxparent.trigger('resize_scroll.xdsoft_scroller', [percentage, true]);
                        }
                        percentage = percentage > 1 ? 1 : (percentage < 0 || isNaN(percentage)) ? 0 : percentage;

                        scroller.css('margin-top', maximumOffset * percentage);

                        setTimeout(function () {
                            timebox.css('marginTop', -parseInt((timebox[0].offsetHeight - parentHeight) * percentage, 10));
                        }, 10);
                    })
                    .on('resize_scroll.xdsoft_scroller', function (event, percentage, noTriggerScroll) {
                        var percent, sh;
                        parentHeight = timeboxparent[0].clientHeight;
                        height = timebox[0].offsetHeight;
                        percent = parentHeight / height;
                        sh = percent * scrollbar[0].offsetHeight;
                        if (percent > 1) {
                            scroller.hide();
                        } else {
                            scroller.show();
                            scroller.css('height', parseInt(sh > 10 ? sh : 10, 10));
                            maximumOffset = scrollbar[0].offsetHeight - scroller[0].offsetHeight;
                            if (noTriggerScroll !== true) {
                                timeboxparent.trigger('scroll_element.xdsoft_scroller', [percentage || Math.abs(parseInt(timebox.css('marginTop'), 10)) / (height - parentHeight)]);
                            }
                        }
                    });

                timeboxparent.on('wheel', function (event) {
                    var top = Math.abs(parseInt(timebox.css('marginTop'), 10));

                    top = top - (event.deltaY * 20);
                    if (top < 0) {
                        top = 0;
                    }

                    timeboxparent.trigger('scroll_element.xdsoft_scroller', [top / (height - parentHeight)]);
                    event.stopPropagation();
                    return false;
                });

                timeboxparent.on('touchstart', function (event) {
                    start = pointerEventToXY(event);
                    startTop = Math.abs(parseInt(timebox.css('marginTop'), 10));
                });

                timeboxparent.on('touchmove', function (event) {
                    if (start) {
                        event.preventDefault();
                        var coord = pointerEventToXY(event);
                        timeboxparent.trigger('scroll_element.xdsoft_scroller', [(startTop - (coord.y - start.y)) / (height - parentHeight)]);
                    }
                });

                timeboxparent.on('touchend touchcancel', function (event) {
                    start = false;
                    startTop = 0;
                });
            }
            timeboxparent.trigger('resize_scroll.xdsoft_scroller', [percent]);
        });
    };

    $.fn.datetimepicker = function (opt) {
        var KEY0 = 48,
            KEY9 = 57,
            _KEY0 = 96,
            _KEY9 = 105,
            CTRLKEY = 17,
            DEL = 46,
            ENTER = 13,
            ESC = 27,
            BACKSPACE = 8,
            ARROWLEFT = 37,
            ARROWUP = 38,
            ARROWRIGHT = 39,
            ARROWDOWN = 40,
            TAB = 9,
            F5 = 116,
            AKEY = 65,
            CKEY = 67,
            VKEY = 86,
            ZKEY = 90,
            YKEY = 89,
            ctrlDown = false,
            options = ($.isPlainObject(opt) || !opt) ? $.extend(true, {}, default_options, opt) : $.extend(true, {}, default_options),

            lazyInitTimer = 0,
            createDateTimePicker,
            destroyDateTimePicker,

            lazyInit = function (input) {
                if (typeof options.i18n[options.lang] === 'undefined') {
                    options.lang = 'en';
                }
                input
                    .on('open.xdsoft focusin.xdsoft mousedown.xdsoft', function initOnActionCallback(event) {
                        if (input.is(':disabled') || input.data('xdsoft_datetimepicker')) {
                            return;
                        }
                        clearTimeout(lazyInitTimer);
                        lazyInitTimer = setTimeout(function () {

                            if (!input.data('xdsoft_datetimepicker')) {
                                createDateTimePicker(input);
                            }
                            input
                                .off('open.xdsoft focusin.xdsoft mousedown.xdsoft', initOnActionCallback)
                                .trigger('open.xdsoft');
                        }, 100);
                    });
            };

        createDateTimePicker = function (input) {
            var datetimepicker = $('<div ' + (options.id ? 'id="' + options.id + '"' : '') + ' ' + (options.style ? 'style="' + options.style + '"' : '') + ' class="xdsoft_datetimepicker xdsoft_' + options.theme + ' xdsoft_noselect ' + (options.weeks ? ' xdsoft_showweeks' : '') + options.className + '"></div>'),
                xdsoft_copyright = $('<div class="xdsoft_copyright"><a target="_blank" href="http://xdsoft.net/jqplugins/datetimepicker/">xdsoft.net</a></div>'),
                datepicker = $('<div class="xdsoft_datepicker active"></div>'),
                mounth_picker = $('<div class="xdsoft_mounthpicker"><button type="button" class="xdsoft_prev"></button><button type="button" class="xdsoft_today_button"></button>' +
                    '<div class="xdsoft_label xdsoft_month"><span></span><i></i></div>' +
                    '<div class="xdsoft_label xdsoft_year"><span></span><i></i></div>' +
                    '<button type="button" class="xdsoft_next"></button></div>'),
                calendar = $('<div class="xdsoft_calendar"></div>'),
                timepicker = $('<div class="xdsoft_timepicker active"><button type="button" class="xdsoft_prev"></button><div class="xdsoft_time_box"></div><button type="button" class="xdsoft_next"></button></div>'),
                timeboxparent = timepicker.find('.xdsoft_time_box').eq(0),
                timebox = $('<div class="xdsoft_time_variant"></div>'),
                /*scrollbar = $('<div class="xdsoft_scrollbar"></div>'),
                 scroller = $('<div class="xdsoft_scroller"></div>'),*/
                monthselect = $('<div class="xdsoft_select xdsoft_monthselect"><div></div></div>'),
                yearselect = $('<div class="xdsoft_select xdsoft_yearselect"><div></div></div>'),
                triggerAfterOpen = false,
                XDSoft_datetime,
                //scroll_element,
                xchangeTimer,
                timerclick,
                current_time_index,
                setPos,
                timer = 0,
                timer1 = 0,
                _xdsoft_datetime;

            mounth_picker
                .find('.xdsoft_month span')
                .after(monthselect);
            mounth_picker
                .find('.xdsoft_year span')
                .after(yearselect);

            mounth_picker
                .find('.xdsoft_month,.xdsoft_year')
                .on('mousedown.xdsoft', function (event) {
                    var select = $(this).find('.xdsoft_select').eq(0),
                        val = 0,
                        top = 0,
                        visible = select.is(':visible'),
                        items,
                        i;

                    mounth_picker
                        .find('.xdsoft_select')
                        .hide();
                    if (_xdsoft_datetime.currentTime) {
                        val = _xdsoft_datetime.currentTime[$(this).hasClass('xdsoft_month') ? 'getMonth' : 'getFullYear']();
                    }

                    select[visible ? 'hide' : 'show']();
                    for (items = select.find('div.xdsoft_option'), i = 0; i < items.length; i += 1) {
                        if (items.eq(i).data('value') === val) {
                            break;
                        } else {
                            top += items[0].offsetHeight;
                        }
                    }

                    select.xdsoftScroller(top / (select.children()[0].offsetHeight - (select[0].clientHeight)));
                    event.stopPropagation();
                    return false;
                });

            mounth_picker
                .find('.xdsoft_select')
                .xdsoftScroller()
                .on('mousedown.xdsoft', function (event) {
                    event.stopPropagation();
                    event.preventDefault();
                })
                .on('mousedown.xdsoft', '.xdsoft_option', function (event) {
                    var year = _xdsoft_datetime.currentTime.getFullYear();
                    if (_xdsoft_datetime && _xdsoft_datetime.currentTime) {
                        _xdsoft_datetime.currentTime[$(this).parent().parent().hasClass('xdsoft_monthselect') ? 'setMonth' : 'setFullYear']($(this).data('value'));
                    }

                    $(this).parent().parent().hide();

                    datetimepicker.trigger('xchange.xdsoft');
                    if (options.onChangeMonth && $.isFunction(options.onChangeMonth)) {
                        options.onChangeMonth.call(datetimepicker, _xdsoft_datetime.currentTime, datetimepicker.data('input'));
                    }

                    if (year !== _xdsoft_datetime.currentTime.getFullYear() && $.isFunction(options.onChangeYear)) {
                        options.onChangeYear.call(datetimepicker, _xdsoft_datetime.currentTime, datetimepicker.data('input'));
                    }
                });

            datetimepicker.setOptions = function (_options) {
                options = $.extend(true, {}, options, _options);

                if (_options.allowTimes && $.isArray(_options.allowTimes) && _options.allowTimes.length) {
                    options.allowTimes = $.extend(true, [], _options.allowTimes);
                }

                if (_options.weekends && $.isArray(_options.weekends) && _options.weekends.length) {
                    options.weekends = $.extend(true, [], _options.weekends);
                }

                if (_options.disabledDates && $.isArray(_options.disabledDates) && _options.disabledDates.length) {
                    options.disabledDates = $.extend(true, [], _options.disabledDates);
                }

                if ((options.open || options.opened) && (!options.inline)) {
                    input.trigger('open.xdsoft');
                }

                if (options.inline) {
                    triggerAfterOpen = true;
                    datetimepicker.addClass('xdsoft_inline');
                    input.after(datetimepicker).hide();
                }

                if (options.inverseButton) {
                    options.next = 'xdsoft_prev';
                    options.prev = 'xdsoft_next';
                }

                if (options.datepicker) {
                    datepicker.addClass('active');
                } else {
                    datepicker.removeClass('active');
                }

                if (options.timepicker) {
                    timepicker.addClass('active');
                } else {
                    timepicker.removeClass('active');
                }

                if (options.value) {
                    if (input && input.val) {
                        input.val(options.value);
                    }
                    _xdsoft_datetime.setCurrentTime(options.value);
                }

                if (isNaN(options.dayOfWeekStart)) {
                    options.dayOfWeekStart = 0;
                } else {
                    options.dayOfWeekStart = parseInt(options.dayOfWeekStart, 10) % 7;
                }

                if (!options.timepickerScrollbar) {
                    timeboxparent.xdsoftScroller('hide');
                }

                if (options.minDate && /^-(.*)$/.test(options.minDate)) {
                    options.minDate = _xdsoft_datetime.strToDateTime(options.minDate).dateFormat(options.formatDate);
                }

                if (options.maxDate && /^\+(.*)$/.test(options.maxDate)) {
                    options.maxDate = _xdsoft_datetime.strToDateTime(options.maxDate).dateFormat(options.formatDate);
                }

                mounth_picker
                    .find('.xdsoft_today_button')
                    .css('visibility', !options.todayButton ? 'hidden' : 'visible');

                if (options.mask) {
                    var e,
                        getCaretPos = function (input) {
                            try {
                                if (document.selection && document.selection.createRange) {
                                    var range = document.selection.createRange();
                                    return range.getBookmark().charCodeAt(2) - 2;
                                }
                                if (input.setSelectionRange) {
                                    return input.selectionStart;
                                }
                            } catch (e) {
                                return 0;
                            }
                        },
                        setCaretPos = function (node, pos) {
                            node = (typeof node === "string" || node instanceof String) ? document.getElementById(node) : node;
                            if (!node) {
                                return false;
                            }
                            if (node.createTextRange) {
                                var textRange = node.createTextRange();
                                textRange.collapse(true);
                                textRange.moveEnd('character', pos);
                                textRange.moveStart('character', pos);
                                textRange.select();
                                return true;
                            }
                            if (node.setSelectionRange) {
                                node.setSelectionRange(pos, pos);
                                return true;
                            }
                            return false;
                        },
                        isValidValue = function (mask, value) {
                            var reg = mask
                                .replace(/([\[\]\/\{\}\(\)\-\.\+]{1})/g, '\\$1')
                                .replace(/_/g, '{digit+}')
                                .replace(/([0-9]{1})/g, '{digit$1}')
                                .replace(/\{digit([0-9]{1})\}/g, '[0-$1_]{1}')
                                .replace(/\{digit[\+]\}/g, '[0-9_]{1}');
                            return (new RegExp(reg)).test(value);
                        };
                    input.off('keydown.xdsoft');

                    if (options.mask === true) {
                        options.mask = options.format
                            .replace(/Y/g, '9999')
                            .replace(/F/g, '9999')
                            .replace(/m/g, '19')
                            .replace(/d/g, '39')
                            .replace(/H/g, '29')
                            .replace(/i/g, '59')
                            .replace(/s/g, '59');
                    }

                    if ($.type(options.mask) === 'string') {
                        if (!isValidValue(options.mask, input.val())) {
                            input.val(options.mask.replace(/[0-9]/g, '_'));
                        }

                        input.on('keydown.xdsoft', function (event) {
                            var val = this.value,
                                key = event.which,
                                pos,
                                digit;

                            if (((key >= KEY0 && key <= KEY9) || (key >= _KEY0 && key <= _KEY9)) || (key === BACKSPACE || key === DEL)) {
                                pos = getCaretPos(this);
                                digit = (key !== BACKSPACE && key !== DEL) ? String.fromCharCode((_KEY0 <= key && key <= _KEY9) ? key - KEY0 : key) : '_';

                                if ((key === BACKSPACE || key === DEL) && pos) {
                                    pos -= 1;
                                    digit = '_';
                                }

                                while (/[^0-9_]/.test(options.mask.substr(pos, 1)) && pos < options.mask.length && pos > 0) {
                                    pos += (key === BACKSPACE || key === DEL) ? -1 : 1;
                                }

                                val = val.substr(0, pos) + digit + val.substr(pos + 1);
                                if ($.trim(val) === '') {
                                    val = options.mask.replace(/[0-9]/g, '_');
                                } else {
                                    if (pos === options.mask.length) {
                                        event.preventDefault();
                                        return false;
                                    }
                                }

                                pos += (key === BACKSPACE || key === DEL) ? 0 : 1;
                                while (/[^0-9_]/.test(options.mask.substr(pos, 1)) && pos < options.mask.length && pos > 0) {
                                    pos += (key === BACKSPACE || key === DEL) ? -1 : 1;
                                }

                                if (isValidValue(options.mask, val)) {
                                    this.value = val;
                                    setCaretPos(this, pos);
                                } else if ($.trim(val) === '') {
                                    this.value = options.mask.replace(/[0-9]/g, '_');
                                } else {
                                    input.trigger('error_input.xdsoft');
                                }
                            } else {
                                if (([AKEY, CKEY, VKEY, ZKEY, YKEY].indexOf(key) !== -1 && ctrlDown) || [ESC, ARROWUP, ARROWDOWN, ARROWLEFT, ARROWRIGHT, F5, CTRLKEY, TAB, ENTER].indexOf(key) !== -1) {
                                    return true;
                                }
                            }

                            event.preventDefault();
                            return false;
                        });
                    }
                }
                if (options.validateOnBlur) {
                    input
                        .off('blur.xdsoft')
                        .on('blur.xdsoft', function () {
                            if (options.allowBlank && !$.trim($(this).val()).length) {
                                $(this).val(null);
                                datetimepicker.data('xdsoft_datetime').empty();
                            } else if (!Date.parseDate($(this).val(), options.format)) {
                                $(this).val((_xdsoft_datetime.now()).dateFormat(options.format));
                                datetimepicker.data('xdsoft_datetime').setCurrentTime($(this).val());
                            } else {
                                datetimepicker.data('xdsoft_datetime').setCurrentTime($(this).val());
                            }
                            datetimepicker.trigger('changedatetime.xdsoft');
                        });
                }
                options.dayOfWeekStartPrev = (options.dayOfWeekStart === 0) ? 6 : options.dayOfWeekStart - 1;

                datetimepicker
                    .trigger('xchange.xdsoft')
                    .trigger('afterOpen.xdsoft');
            };

            datetimepicker
                .data('options', options)
                .on('mousedown.xdsoft', function (event) {
                    event.stopPropagation();
                    event.preventDefault();
                    yearselect.hide();
                    monthselect.hide();
                    return false;
                });

            //scroll_element = timepicker.find('.xdsoft_time_box');
            timeboxparent.append(timebox);
            timeboxparent.xdsoftScroller();

            datetimepicker.on('afterOpen.xdsoft', function () {
                timeboxparent.xdsoftScroller();
            });

            datetimepicker
                .append(datepicker)
                .append(timepicker);

            if (options.withoutCopyright !== true) {
                datetimepicker
                    .append(xdsoft_copyright);
            }

            datepicker
                .append(mounth_picker)
                .append(calendar);

            $(options.parentID)
                .append(datetimepicker);

            XDSoft_datetime = function () {
                var _this = this;
                _this.now = function (norecursion) {
                    var d = new Date(),
                        date,
                        time;

                    if (!norecursion && options.defaultDate) {
                        date = _this.strToDate(options.defaultDate);
                        d.setFullYear(date.getFullYear());
                        d.setMonth(date.getMonth());
                        d.setDate(date.getDate());
                    }

                    if (options.yearOffset) {
                        d.setFullYear(d.getFullYear() + options.yearOffset);
                    }

                    if (!norecursion && options.defaultTime) {
                        time = _this.strtotime(options.defaultTime);
                        d.setHours(time.getHours());
                        d.setMinutes(time.getMinutes());
                    }

                    return d;
                };

                _this.isValidDate = function (d) {
                    if (Object.prototype.toString.call(d) !== "[object Date]") {
                        return false;
                    }
                    return !isNaN(d.getTime());
                };

                _this.setCurrentTime = function (dTime) {
                    _this.currentTime = (typeof dTime === 'string') ? _this.strToDateTime(dTime) : _this.isValidDate(dTime) ? dTime : _this.now();
                    datetimepicker.trigger('xchange.xdsoft');
                };

                _this.empty = function () {
                    _this.currentTime = null;
                };

                _this.getCurrentTime = function (dTime) {
                    return _this.currentTime;
                };

                _this.nextMonth = function () {
                    var month = _this.currentTime.getMonth() + 1,
                        year;
                    if (month === 12) {
                        _this.currentTime.setFullYear(_this.currentTime.getFullYear() + 1);
                        month = 0;
                    }

                    year = _this.currentTime.getFullYear();

                    _this.currentTime.setDate(
                        Math.min(
                            new Date(_this.currentTime.getFullYear(), month + 1, 0).getDate(),
                            _this.currentTime.getDate()
                        )
                    );
                    _this.currentTime.setMonth(month);

                    if (options.onChangeMonth && $.isFunction(options.onChangeMonth)) {
                        options.onChangeMonth.call(datetimepicker, _xdsoft_datetime.currentTime, datetimepicker.data('input'));
                    }

                    if (year !== _this.currentTime.getFullYear() && $.isFunction(options.onChangeYear)) {
                        options.onChangeYear.call(datetimepicker, _xdsoft_datetime.currentTime, datetimepicker.data('input'));
                    }

                    datetimepicker.trigger('xchange.xdsoft');
                    return month;
                };

                _this.prevMonth = function () {
                    var month = _this.currentTime.getMonth() - 1;
                    if (month === -1) {
                        _this.currentTime.setFullYear(_this.currentTime.getFullYear() - 1);
                        month = 11;
                    }
                    _this.currentTime.setDate(
                        Math.min(
                            new Date(_this.currentTime.getFullYear(), month + 1, 0).getDate(),
                            _this.currentTime.getDate()
                        )
                    );
                    _this.currentTime.setMonth(month);
                    if (options.onChangeMonth && $.isFunction(options.onChangeMonth)) {
                        options.onChangeMonth.call(datetimepicker, _xdsoft_datetime.currentTime, datetimepicker.data('input'));
                    }
                    datetimepicker.trigger('xchange.xdsoft');
                    return month;
                };

                _this.getWeekOfYear = function (datetime) {
                    var onejan = new Date(datetime.getFullYear(), 0, 1);
                    return Math.ceil((((datetime - onejan) / 86400000) + onejan.getDay() + 1) / 7);
                };

                _this.strToDateTime = function (sDateTime) {
                    var tmpDate = [], timeOffset, currentTime;

                    if (sDateTime && sDateTime instanceof Date && _this.isValidDate(sDateTime)) {
                        return sDateTime;
                    }

                    tmpDate = /^(\+|\-)(.*)$/.exec(sDateTime);
                    if (tmpDate) {
                        tmpDate[2] = Date.parseDate(tmpDate[2], options.formatDate);
                    }
                    if (tmpDate && tmpDate[2]) {
                        timeOffset = tmpDate[2].getTime() - (tmpDate[2].getTimezoneOffset()) * 60000;
                        currentTime = new Date((_xdsoft_datetime.now()).getTime() + parseInt(tmpDate[1] + '1', 10) * timeOffset);
                    } else {
                        currentTime = sDateTime ? Date.parseDate(sDateTime, options.format) : _this.now();
                    }

                    if (!_this.isValidDate(currentTime)) {
                        currentTime = _this.now();
                    }

                    return currentTime;
                };

                _this.strToDate = function (sDate) {
                    if (sDate && sDate instanceof Date && _this.isValidDate(sDate)) {
                        return sDate;
                    }

                    var currentTime = sDate ? Date.parseDate(sDate, options.formatDate) : _this.now(true);
                    if (!_this.isValidDate(currentTime)) {
                        currentTime = _this.now(true);
                    }
                    return currentTime;
                };

                _this.strtotime = function (sTime) {
                    if (sTime && sTime instanceof Date && _this.isValidDate(sTime)) {
                        return sTime;
                    }
                    var currentTime = sTime ? Date.parseDate(sTime, options.formatTime) : _this.now(true);
                    if (!_this.isValidDate(currentTime)) {
                        currentTime = _this.now(true);
                    }
                    return currentTime;
                };

                _this.str = function () {
                    return _this.currentTime.dateFormat(options.format);
                };
                _this.currentTime = this.now();
            };

            _xdsoft_datetime = new XDSoft_datetime();

            mounth_picker
                .find('.xdsoft_today_button')
                .on('mousedown.xdsoft', function () {
                    datetimepicker.data('changed', true);
                    _xdsoft_datetime.setCurrentTime(0);
                    datetimepicker.trigger('afterOpen.xdsoft');
                }).on('dblclick.xdsoft', function () {
                input.val(_xdsoft_datetime.str());
                datetimepicker.trigger('close.xdsoft');
            });
            mounth_picker
                .find('.xdsoft_prev,.xdsoft_next')
                .on('mousedown.xdsoft', function () {
                    var $this = $(this),
                        timer = 0,
                        stop = false;

                    (function arguments_callee1(v) {
                        var month = _xdsoft_datetime.currentTime.getMonth();
                        if ($this.hasClass(options.next)) {
                            _xdsoft_datetime.nextMonth();
                        } else if ($this.hasClass(options.prev)) {
                            _xdsoft_datetime.prevMonth();
                        }
                        if (options.monthChangeSpinner) {
                            if (!stop) {
                                timer = setTimeout(arguments_callee1, v || 100);
                            }
                        }
                    }(500));

                    $([document.body, window]).on('mouseup.xdsoft', function arguments_callee2() {
                        clearTimeout(timer);
                        stop = true;
                        $([document.body, window]).off('mouseup.xdsoft', arguments_callee2);
                    });
                });

            timepicker
                .find('.xdsoft_prev,.xdsoft_next')
                .on('mousedown.xdsoft', function () {
                    var $this = $(this),
                        timer = 0,
                        stop = false,
                        period = 110;
                    (function arguments_callee4(v) {
                        var pheight = timeboxparent[0].clientHeight,
                            height = timebox[0].offsetHeight,
                            top = Math.abs(parseInt(timebox.css('marginTop'), 10));
                        if ($this.hasClass(options.next) && (height - pheight) - options.timeHeightInTimePicker >= top) {
                            timebox.css('marginTop', '-' + (top + options.timeHeightInTimePicker) + 'px');
                        } else if ($this.hasClass(options.prev) && top - options.timeHeightInTimePicker >= 0) {
                            timebox.css('marginTop', '-' + (top - options.timeHeightInTimePicker) + 'px');
                        }
                        timeboxparent.trigger('scroll_element.xdsoft_scroller', [Math.abs(parseInt(timebox.css('marginTop'), 10) / (height - pheight))]);
                        period = (period > 10) ? 10 : period - 10;
                        if (!stop) {
                            timer = setTimeout(arguments_callee4, v || period);
                        }
                    }(500));
                    $([document.body, window]).on('mouseup.xdsoft', function arguments_callee5() {
                        clearTimeout(timer);
                        stop = true;
                        $([document.body, window])
                            .off('mouseup.xdsoft', arguments_callee5);
                    });
                });

            xchangeTimer = 0;
            // base handler - generating a calendar and timepicker
            datetimepicker
                .on('xchange.xdsoft', function (event) {
                    clearTimeout(xchangeTimer);
                    xchangeTimer = setTimeout(function () {
                        var table = '',
                            start = new Date(_xdsoft_datetime.currentTime.getFullYear(), _xdsoft_datetime.currentTime.getMonth(), 1, 12, 0, 0),
                            i = 0,
                            j,
                            today = _xdsoft_datetime.now(),
                            maxDate = false,
                            minDate = false,
                            d,
                            y,
                            m,
                            w,
                            classes = [],
                            customDateSettings,
                            newRow = true,
                            time = '',
                            h = '',
                            line_time;

                        while (start.getDay() !== options.dayOfWeekStart) {
                            start.setDate(start.getDate() - 1);
                        }

                        table += '<table><thead><tr>';

                        if (options.weeks) {
                            table += '<th></th>';
                        }

                        for (j = 0; j < 7; j += 1) {
                            table += '<th>' + options.i18n[options.lang].dayOfWeek[(j + options.dayOfWeekStart) % 7] + '</th>';
                        }

                        table += '</tr></thead>';
                        table += '<tbody>';

                        if (options.maxDate !== false) {
                            maxDate = _xdsoft_datetime.strToDate(options.maxDate);
                            maxDate = new Date(maxDate.getFullYear(), maxDate.getMonth(), maxDate.getDate(), 23, 59, 59, 999);
                        }

                        if (options.minDate !== false) {
                            minDate = _xdsoft_datetime.strToDate(options.minDate);
                            minDate = new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate());
                        }

                        while (i < _xdsoft_datetime.currentTime.countDaysInMonth() || start.getDay() !== options.dayOfWeekStart || _xdsoft_datetime.currentTime.getMonth() === start.getMonth()) {
                            classes = [];
                            i += 1;

                            d = start.getDate();
                            y = start.getFullYear();
                            m = start.getMonth();
                            w = _xdsoft_datetime.getWeekOfYear(start);

                            classes.push('xdsoft_date');

                            if (options.beforeShowDay && $.isFunction(options.beforeShowDay.call)) {
                                customDateSettings = options.beforeShowDay.call(datetimepicker, start);
                            } else {
                                customDateSettings = null;
                            }

                            if ((maxDate !== false && start > maxDate) || (minDate !== false && start < minDate) || (customDateSettings && customDateSettings[0] === false)) {
                                classes.push('xdsoft_disabled');
                            } else if (options.disabledDates.indexOf(start.dateFormat(options.formatDate)) !== -1) {
                                classes.push('xdsoft_disabled');
                            }

                            if (customDateSettings && customDateSettings[1] !== "") {
                                classes.push(customDateSettings[1]);
                            }

                            if (_xdsoft_datetime.currentTime.getMonth() !== m) {
                                classes.push('xdsoft_other_month');
                            }

                            if ((options.defaultSelect || datetimepicker.data('changed')) && _xdsoft_datetime.currentTime.dateFormat(options.formatDate) === start.dateFormat(options.formatDate)) {
                                classes.push('xdsoft_current');
                            }

                            if (today.dateFormat(options.formatDate) === start.dateFormat(options.formatDate)) {
                                classes.push('xdsoft_today');
                            }

                            if (start.getDay() === 0 || start.getDay() === 6 || ~options.weekends.indexOf(start.dateFormat(options.formatDate))) {
                                classes.push('xdsoft_weekend');
                            }

                            if (options.beforeShowDay && $.isFunction(options.beforeShowDay)) {
                                classes.push(options.beforeShowDay(start));
                            }

                            if (newRow) {
                                table += '<tr>';
                                newRow = false;
                                if (options.weeks) {
                                    table += '<th>' + w + '</th>';
                                }
                            }

                            table += '<td data-date="' + d + '" data-month="' + m + '" data-year="' + y + '"' + ' class="xdsoft_date xdsoft_day_of_week' + start.getDay() + ' ' + classes.join(' ') + '">' +
                                '<div>' + d + '</div>' +
                                '</td>';

                            if (start.getDay() === options.dayOfWeekStartPrev) {
                                table += '</tr>';
                                newRow = true;
                            }

                            start.setDate(d + 1);
                        }
                        table += '</tbody></table>';

                        calendar.html(table);

                        mounth_picker.find('.xdsoft_label span').eq(0).text(options.i18n[options.lang].months[_xdsoft_datetime.currentTime.getMonth()]);
                        mounth_picker.find('.xdsoft_label span').eq(1).text(_xdsoft_datetime.currentTime.getFullYear());

                        // generate timebox
                        time = '';
                        h = '';
                        m = '';
                        line_time = function line_time(h, m) {
                            var now = _xdsoft_datetime.now();
                            now.setHours(h);
                            h = parseInt(now.getHours(), 10);
                            now.setMinutes(m);
                            m = parseInt(now.getMinutes(), 10);
                            var optionDateTime = new Date(_xdsoft_datetime.currentTime);
                            optionDateTime.setHours(h);
                            optionDateTime.setMinutes(m);
                            classes = [];
                            if ((options.minDateTime !== false && options.minDateTime > optionDateTime) || (options.maxTime !== false && _xdsoft_datetime.strtotime(options.maxTime).getTime() < now.getTime()) || (options.minTime !== false && _xdsoft_datetime.strtotime(options.minTime).getTime() > now.getTime())) {
                                classes.push('xdsoft_disabled');
                            }
                            if ((options.initTime || options.defaultSelect || datetimepicker.data('changed')) && parseInt(_xdsoft_datetime.currentTime.getHours(), 10) === parseInt(h, 10) && (options.step > 59 || Math[options.roundTime](_xdsoft_datetime.currentTime.getMinutes() / options.step) * options.step === parseInt(m, 10))) {
                                if (options.defaultSelect || datetimepicker.data('changed')) {
                                    classes.push('xdsoft_current');
                                } else if (options.initTime) {
                                    classes.push('xdsoft_init_time');
                                }
                            }
                            if (parseInt(today.getHours(), 10) === parseInt(h, 10) && parseInt(today.getMinutes(), 10) === parseInt(m, 10)) {
                                classes.push('xdsoft_today');
                            }
                            time += '<div class="xdsoft_time ' + classes.join(' ') + '" data-hour="' + h + '" data-minute="' + m + '">' + now.dateFormat(options.formatTime) + '</div>';
                        };

                        if (!options.allowTimes || !$.isArray(options.allowTimes) || !options.allowTimes.length) {
                            for (i = 0, j = 0; i < (options.hours12 ? 12 : 24); i += 1) {
                                for (j = 0; j < 60; j += options.step) {
                                    h = (i < 10 ? '0' : '') + i;
                                    m = (j < 10 ? '0' : '') + j;
                                    line_time(h, m);
                                }
                            }
                        } else {
                            for (i = 0; i < options.allowTimes.length; i += 1) {
                                h = _xdsoft_datetime.strtotime(options.allowTimes[i]).getHours();
                                m = _xdsoft_datetime.strtotime(options.allowTimes[i]).getMinutes();
                                line_time(h, m);
                            }
                        }

                        timebox.html(time);

                        opt = '';
                        i = 0;

                        for (i = parseInt(options.yearStart, 10) + options.yearOffset; i <= parseInt(options.yearEnd, 10) + options.yearOffset; i += 1) {
                            opt += '<div class="xdsoft_option ' + (_xdsoft_datetime.currentTime.getFullYear() === i ? 'xdsoft_current' : '') + '" data-value="' + i + '">' + i + '</div>';
                        }
                        yearselect.children().eq(0)
                            .html(opt);

                        for (i = 0, opt = ''; i <= 11; i += 1) {
                            opt += '<div class="xdsoft_option ' + (_xdsoft_datetime.currentTime.getMonth() === i ? 'xdsoft_current' : '') + '" data-value="' + i + '">' + options.i18n[options.lang].months[i] + '</div>';
                        }
                        monthselect.children().eq(0).html(opt);
                        $(datetimepicker)
                            .trigger('generate.xdsoft');
                    }, 10);
                    event.stopPropagation();
                })
                .on('afterOpen.xdsoft', function () {
                    if (options.timepicker) {
                        var classType, pheight, height, top;
                        if (timebox.find('.xdsoft_current').length) {
                            classType = '.xdsoft_current';
                        } else if (timebox.find('.xdsoft_init_time').length) {
                            classType = '.xdsoft_init_time';
                        }
                        if (classType) {
                            pheight = timeboxparent[0].clientHeight;
                            height = timebox[0].offsetHeight;
                            top = timebox.find(classType).index() * options.timeHeightInTimePicker + 1;
                            if ((height - pheight) < top) {
                                top = height - pheight;
                            }
                            timeboxparent.trigger('scroll_element.xdsoft_scroller', [parseInt(top, 10) / (height - pheight)]);
                        } else {
                            timeboxparent.trigger('scroll_element.xdsoft_scroller', [0]);
                        }
                    }
                });

            timerclick = 0;
            calendar
                .on('click.xdsoft', 'td', function (xdevent) {
                    xdevent.stopPropagation();  // Prevents closing of Pop-ups, Modals and Flyouts in Bootstrap
                    timerclick += 1;
                    var $this = $(this),
                        currentTime = _xdsoft_datetime.currentTime;

                    if (currentTime === undefined || currentTime === null) {
                        _xdsoft_datetime.currentTime = _xdsoft_datetime.now();
                        currentTime = _xdsoft_datetime.currentTime;
                    }

                    if ($this.hasClass('xdsoft_disabled')) {
                        return false;
                    }

                    currentTime.setDate(1);
                    currentTime.setFullYear($this.data('year'));
                    currentTime.setMonth($this.data('month'));
                    currentTime.setDate($this.data('date'));

                    datetimepicker.trigger('select.xdsoft', [currentTime]);

                    input.val(_xdsoft_datetime.str());
                    if ((timerclick > 1 || (options.closeOnDateSelect === true || (options.closeOnDateSelect === 0 && !options.timepicker))) && !options.inline) {
                        datetimepicker.trigger('close.xdsoft');
                    }

                    if (options.onSelectDate && $.isFunction(options.onSelectDate)) {
                        options.onSelectDate.call(datetimepicker, _xdsoft_datetime.currentTime, datetimepicker.data('input'), xdevent);
                    }

                    datetimepicker.data('changed', true);
                    datetimepicker.trigger('xchange.xdsoft');
                    datetimepicker.trigger('changedatetime.xdsoft');
                    setTimeout(function () {
                        timerclick = 0;
                    }, 200);
                });

            timebox
                .on('click.xdsoft', 'div', function (xdevent) {
                    xdevent.stopPropagation();
                    var $this = $(this),
                        currentTime = _xdsoft_datetime.currentTime;

                    if (currentTime === undefined || currentTime === null) {
                        _xdsoft_datetime.currentTime = _xdsoft_datetime.now();
                        currentTime = _xdsoft_datetime.currentTime;
                    }

                    if ($this.hasClass('xdsoft_disabled')) {
                        return false;
                    }
                    currentTime.setHours($this.data('hour'));
                    currentTime.setMinutes($this.data('minute'));
                    datetimepicker.trigger('select.xdsoft', [currentTime]);

                    datetimepicker.data('input').val(_xdsoft_datetime.str());
                    if (!options.inline) {
                        datetimepicker.trigger('close.xdsoft');
                    }

                    if (options.onSelectTime && $.isFunction(options.onSelectTime)) {
                        options.onSelectTime.call(datetimepicker, _xdsoft_datetime.currentTime, datetimepicker.data('input'), xdevent);
                    }
                    datetimepicker.data('changed', true);
                    datetimepicker.trigger('xchange.xdsoft');
                    datetimepicker.trigger('changedatetime.xdsoft');
                });


            datepicker
                .on('wheel.xdsoft', function (event) {
                    if (!options.scrollMonth) {
                        return true;
                    }
                    if (event.deltaY < 0) {
                        _xdsoft_datetime.nextMonth();
                    } else {
                        _xdsoft_datetime.prevMonth();
                    }
                    return false;
                });

            input
                .on('wheel.xdsoft', function (event) {
                    if (!options.scrollInput) {
                        return true;
                    }
                    if (!options.datepicker && options.timepicker) {
                        current_time_index = timebox.find('.xdsoft_current').length ? timebox.find('.xdsoft_current').eq(0).index() : 0;
                        if (current_time_index + event.deltaY >= 0 && current_time_index + event.deltaY < timebox.children().length) {
                            current_time_index += event.deltaY;
                        }
                        if (timebox.children().eq(current_time_index).length) {
                            timebox.children().eq(current_time_index).trigger('mousedown');
                        }
                        return false;
                    }
                    if (options.datepicker && !options.timepicker) {
                        datepicker.trigger(event, [event.deltaY, event.deltaX, event.deltaY]);
                        if (input.val) {
                            input.val(_xdsoft_datetime.str());
                        }
                        datetimepicker.trigger('changedatetime.xdsoft');
                        return false;
                    }
                });

            datetimepicker
                .on('changedatetime.xdsoft', function (event) {
                    if (options.onChangeDateTime && $.isFunction(options.onChangeDateTime)) {
                        var $input = datetimepicker.data('input');
                        options.onChangeDateTime.call(datetimepicker, _xdsoft_datetime.currentTime, $input, event);
                        delete options.value;
                        $input.trigger('change');
                    }
                })
                .on('generate.xdsoft', function () {
                    if (options.onGenerate && $.isFunction(options.onGenerate)) {
                        options.onGenerate.call(datetimepicker, _xdsoft_datetime.currentTime, datetimepicker.data('input'));
                    }
                    if (triggerAfterOpen) {
                        datetimepicker.trigger('afterOpen.xdsoft');
                        triggerAfterOpen = false;
                    }
                })
                .on('click.xdsoft', function (xdevent) {
                    xdevent.stopPropagation();
                });

            current_time_index = 0;

            setPos = function () {
                var offset = datetimepicker.data('input').offset(),
                    top = offset.top + datetimepicker.data('input')[0].offsetHeight - 1, left = offset.left,
                    position = "absolute";
                if (options.fixed) {
                    top -= $(window).scrollTop();
                    left -= $(window).scrollLeft();
                    position = "fixed";
                } else {
                    if (top + datetimepicker[0].offsetHeight > $(window).height() + $(window).scrollTop()) {
                        top = offset.top - datetimepicker[0].offsetHeight + 1;
                    }
                    if (top < 0) {
                        top = 0;
                    }
                    if (left + datetimepicker[0].offsetWidth > $(window).width()) {
                        left = $(window).width() - datetimepicker[0].offsetWidth;
                    }
                }
                datetimepicker.css({
                    left: left,
                    top: top,
                    position: position
                });
            };
            datetimepicker
                .on('open.xdsoft', function (event) {
                    var onShow = true;
                    if (options.onShow && $.isFunction(options.onShow)) {
                        onShow = options.onShow.call(datetimepicker, _xdsoft_datetime.currentTime, datetimepicker.data('input'), event);
                    }
                    if (onShow !== false) {
                        datetimepicker.show();
                        setPos();
                        $(window)
                            .off('resize.xdsoft', setPos)
                            .on('resize.xdsoft', setPos);

                        if (options.closeOnWithoutClick) {
                            $([document.body, window]).on('mousedown.xdsoft', function arguments_callee6() {
                                datetimepicker.trigger('close.xdsoft');
                                $([document.body, window]).off('mousedown.xdsoft', arguments_callee6);
                            });
                        }
                    }
                })
                .on('close.xdsoft', function (event) {
                    var onClose = true;
                    mounth_picker
                        .find('.xdsoft_month,.xdsoft_year')
                        .find('.xdsoft_select')
                        .hide();
                    if (options.onClose && $.isFunction(options.onClose)) {
                        onClose = options.onClose.call(datetimepicker, _xdsoft_datetime.currentTime, datetimepicker.data('input'), event);
                    }
                    if (onClose !== false && !options.opened && !options.inline) {
                        datetimepicker.hide();
                    }
                    event.stopPropagation();
                })
                .on('toggle.xdsoft', function (event) {
                    if (datetimepicker.is(':visible')) {
                        datetimepicker.trigger('close.xdsoft');
                    } else {
                        datetimepicker.trigger('open.xdsoft');
                    }
                })
                .data('input', input);

            timer = 0;
            timer1 = 0;

            datetimepicker.data('xdsoft_datetime', _xdsoft_datetime);
            datetimepicker.setOptions(options);

            function getCurrentValue() {

                var ct = false, time;

                if (options.startDate) {
                    ct = _xdsoft_datetime.strToDate(options.startDate);
                } else {
                    ct = options.value || ((input && input.val && input.val()) ? input.val() : '');
                    if (ct) {
                        ct = _xdsoft_datetime.strToDateTime(ct);
                    } else if (options.defaultDate) {
                        ct = _xdsoft_datetime.strToDate(options.defaultDate);
                        if (options.defaultTime) {
                            time = _xdsoft_datetime.strtotime(options.defaultTime);
                            ct.setHours(time.getHours());
                            ct.setMinutes(time.getMinutes());
                        }
                    }
                }

                if (ct && _xdsoft_datetime.isValidDate(ct)) {
                    datetimepicker.data('changed', true);
                } else {
                    ct = '';
                }

                return ct || 0;
            }

            _xdsoft_datetime.setCurrentTime(getCurrentValue());

            input
                .data('xdsoft_datetimepicker', datetimepicker)
                .on('open.xdsoft focusin.xdsoft mousedown.xdsoft', function (event) {
                    if (input.is(':disabled') || (input.data('xdsoft_datetimepicker').is(':visible') && options.closeOnInputClick)) {
                        return;
                    }
                    clearTimeout(timer);
                    timer = setTimeout(function () {
                        if (input.is(':disabled')) {
                            return;
                        }

                        triggerAfterOpen = true;
                        _xdsoft_datetime.setCurrentTime(getCurrentValue());

                        datetimepicker.trigger('open.xdsoft');
                    }, 100);
                })
                .on('keydown.xdsoft', function (event) {
                    var val = this.value, elementSelector,
                        key = event.which;
                    if ([ENTER].indexOf(key) !== -1 && options.enterLikeTab) {
                        elementSelector = $("input:visible,textarea:visible");
                        datetimepicker.trigger('close.xdsoft');
                        elementSelector.eq(elementSelector.index(this) + 1).focus();
                        return false;
                    }
                    if ([TAB].indexOf(key) !== -1) {
                        datetimepicker.trigger('close.xdsoft');
                        return true;
                    }
                });
        };
        destroyDateTimePicker = function (input) {
            var datetimepicker = input.data('xdsoft_datetimepicker');
            if (datetimepicker) {
                datetimepicker.data('xdsoft_datetime', null);
                datetimepicker.remove();
                input
                    .data('xdsoft_datetimepicker', null)
                    .off('.xdsoft');
                $(window).off('resize.xdsoft');
                $([window, document.body]).off('mousedown.xdsoft');
                if (input.unmousewheel) {
                    input.unmousewheel();
                }
            }
        };
        $(document)
            .off('keydown.xdsoftctrl keyup.xdsoftctrl')
            .on('keydown.xdsoftctrl', function (e) {
                if (e.keyCode === CTRLKEY) {
                    ctrlDown = true;
                }
            })
            .on('keyup.xdsoftctrl', function (e) {
                if (e.keyCode === CTRLKEY) {
                    ctrlDown = false;
                }
            });
        return this.each(function () {
            var datetimepicker = $(this).data('xdsoft_datetimepicker');
            if (datetimepicker) {
                if ($.type(opt) === 'string') {
                    switch (opt) {
                        case 'show':
                            $(this).select().focus();
                            datetimepicker.trigger('open.xdsoft');
                            break;
                        case 'hide':
                            datetimepicker.trigger('close.xdsoft');
                            break;
                        case 'toggle':
                            datetimepicker.trigger('toggle.xdsoft');
                            break;
                        case 'destroy':
                            destroyDateTimePicker($(this));
                            break;
                        case 'reset':
                            this.value = this.defaultValue;
                            if (!this.value || !datetimepicker.data('xdsoft_datetime').isValidDate(Date.parseDate(this.value, options.format))) {
                                datetimepicker.data('changed', false);
                            }
                            datetimepicker.data('xdsoft_datetime').setCurrentTime(this.value);
                            break;
                    }
                } else {
                    datetimepicker
                        .setOptions(opt);
                }
                return 0;
            }
            if ($.type(opt) !== 'string') {
                if (!options.lazyInit || options.open || options.inline) {
                    createDateTimePicker($(this));
                } else {
                    lazyInit($(this));
                }
            }
        });
    };
    $.fn.datetimepicker.defaults = default_options;
    /*! Copyright (c) 2013 Brandon Aaron (http://brandon.aaron.sh)
     * Licensed under the MIT License (LICENSE.txt).
     *
     * Version: 3.1.12
     *
     * Requires: jQuery 1.2.2+
     */
    (function (a) {
        function b(b) {
            var g = b || window.event, h = i.call(arguments, 1), j = 0, l = 0, m = 0, n = 0, o = 0, p = 0;
            if (b = a.event.fix(g), b.type = "mousewheel", "detail" in g && (m = -1 * g.detail), "wheelDelta" in g && (m = g.wheelDelta), "wheelDeltaY" in g && (m = g.wheelDeltaY), "wheelDeltaX" in g && (l = -1 * g.wheelDeltaX), "axis" in g && g.axis === g.HORIZONTAL_AXIS && (l = -1 * m, m = 0), j = 0 === m ? l : m, "deltaY" in g && (m = -1 * g.deltaY, j = m), "deltaX" in g && (l = g.deltaX, 0 === m && (j = -1 * l)), 0 !== m || 0 !== l) {
                if (1 === g.deltaMode) {
                    var q = a.data(this, "mousewheel-line-height");
                    j *= q, m *= q, l *= q
                } else if (2 === g.deltaMode) {
                    var r = a.data(this, "mousewheel-page-height");
                    j *= r, m *= r, l *= r
                }
                if (n = Math.max(Math.abs(m), Math.abs(l)), (!f || f > n) && (f = n, d(g, n) && (f /= 40)), d(g, n) && (j /= 40, l /= 40, m /= 40), j = Math[j >= 1 ? "floor" : "ceil"](j / f), l = Math[l >= 1 ? "floor" : "ceil"](l / f), m = Math[m >= 1 ? "floor" : "ceil"](m / f), k.settings.normalizeOffset && this.getBoundingClientRect) {
                    var s = this.getBoundingClientRect();
                    o = b.clientX - s.left, p = b.clientY - s.top
                }
                return b.deltaX = l, b.deltaY = m, b.deltaFactor = f, b.offsetX = o, b.offsetY = p, b.deltaMode = 0, h.unshift(b, j, l, m), e && clearTimeout(e), e = setTimeout(c, 200), (a.event.dispatch || a.event.handle).apply(this, h)
            }
        }

        function c() {
            f = null
        }

        function d(a, b) {
            return k.settings.adjustOldDeltas && "mousewheel" === a.type && b % 120 === 0
        }

        var e, f, g = ["wheel", "mousewheel", "DOMMouseScroll", "MozMousePixelScroll"],
            h = "onwheel" in document || document.documentMode >= 9 ? ["wheel"] : ["mousewheel", "DomMouseScroll", "MozMousePixelScroll"],
            i = Array.prototype.slice;
        if (a.event.fixHooks) for (var j = g.length; j;) a.event.fixHooks[g[--j]] = a.event.mouseHooks;
        var k = a.event.special.mousewheel = {
            version: "3.1.12", setup: function () {
                if (this.addEventListener) for (var c = h.length; c;) this.addEventListener(h[--c], b, !1); else this.onmousewheel = b;
                a.data(this, "mousewheel-line-height", k.getLineHeight(this)), a.data(this, "mousewheel-page-height", k.getPageHeight(this))
            }, teardown: function () {
                if (this.removeEventListener) for (var c = h.length; c;) this.removeEventListener(h[--c], b, !1); else this.onmousewheel = null;
                a.removeData(this, "mousewheel-line-height"), a.removeData(this, "mousewheel-page-height")
            }, getLineHeight: function (b) {
                var c = a(b), d = c["offsetParent" in a.fn ? "offsetParent" : "parent"]();
                return d.length || (d = a("body")), parseInt(d.css("fontSize"), 10) || parseInt(c.css("fontSize"), 10) || 16
            }, getPageHeight: function (b) {
                return a(b).height()
            }, settings: {adjustOldDeltas: !0, normalizeOffset: !0}
        };
        a.fn.extend({
            mousewheel: function (a) {
                return a ? this.bind("mousewheel", a) : this.trigger("mousewheel")
            }, unmousewheel: function (a) {
                return this.unbind("mousewheel", a)
            }
        })
    })($);

// Parse and Format Library
//http://www.xaprb.com/blog/2005/12/12/javascript-closures-for-runtime-efficiency/
    /*
     * Copyright (C) 2004 Baron Schwartz <baron at sequent dot org>
     *
     * This program is free software; you can redistribute it and/or modify it
     * under the terms of the GNU Lesser General Public License as published by the
     * Free Software Foundation, version 2.1.
     *
     * This program is distributed in the hope that it will be useful, but WITHOUT
     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
     * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
     * details.
     */
    Date.parseFunctions = {count: 0};
    Date.parseRegexes = [];
    Date.formatFunctions = {count: 0};
    Date.prototype.dateFormat = function (b) {
        if (b == "unixtime") {
            return parseInt(this.getTime() / 1000);
        }
        if (Date.formatFunctions[b] == null) {
            Date.createNewFormat(b);
        }
        var a = Date.formatFunctions[b];
        return this[a]();
    };
    Date.createNewFormat = function (format) {
        var funcName = "format" + Date.formatFunctions.count++;
        Date.formatFunctions[format] = funcName;
        var code = "Date.prototype." + funcName + " = function() {return ";
        var special = false;
        var ch = "";
        for (var i = 0; i < format.length; ++i) {
            ch = format.charAt(i);
            if (!special && ch == "\\") {
                special = true;
            } else {
                if (special) {
                    special = false;
                    code += "'" + String.escape(ch) + "' + ";
                } else {
                    code += Date.getFormatCode(ch);
                }
            }
        }
        eval(code.substring(0, code.length - 3) + ";}");
    };
    Date.getFormatCode = function (a) {
        switch (a) {
            case"d":
                return "String.leftPad(this.getDate(), 2, '0') + ";
            case"D":
                return "Date.dayNames[this.getDay()].substring(0, 3) + ";
            case"j":
                return "this.getDate() + ";
            case"l":
                return "Date.dayNames[this.getDay()] + ";
            case"S":
                return "this.getSuffix() + ";
            case"w":
                return "this.getDay() + ";
            case"z":
                return "this.getDayOfYear() + ";
            case"W":
                return "this.getWeekOfYear() + ";
            case"F":
                return "Date.monthNames[this.getMonth()] + ";
            case"m":
                return "String.leftPad(this.getMonth() + 1, 2, '0') + ";
            case"M":
                return "Date.monthNames[this.getMonth()].substring(0, 3) + ";
            case"n":
                return "(this.getMonth() + 1) + ";
            case"t":
                return "this.getDaysInMonth() + ";
            case"L":
                return "(this.isLeapYear() ? 1 : 0) + ";
            case"Y":
                return "this.getFullYear() + ";
            case"y":
                return "('' + this.getFullYear()).substring(2, 4) + ";
            case"a":
                return "(this.getHours() < 12 ? 'am' : 'pm') + ";
            case"A":
                return "(this.getHours() < 12 ? 'AM' : 'PM') + ";
            case"g":
                return "((this.getHours() %12) ? this.getHours() % 12 : 12) + ";
            case"G":
                return "this.getHours() + ";
            case"h":
                return "String.leftPad((this.getHours() %12) ? this.getHours() % 12 : 12, 2, '0') + ";
            case"H":
                return "String.leftPad(this.getHours(), 2, '0') + ";
            case"i":
                return "String.leftPad(this.getMinutes(), 2, '0') + ";
            case"s":
                return "String.leftPad(this.getSeconds(), 2, '0') + ";
            case"O":
                return "this.getGMTOffset() + ";
            case"T":
                return "this.getTimezone() + ";
            case"Z":
                return "(this.getTimezoneOffset() * -60) + ";
            default:
                return "'" + String.escape(a) + "' + ";
        }
    };
    Date.parseDate = function (a, c) {
        if (c == "unixtime") {
            return new Date(!isNaN(parseInt(a)) ? parseInt(a) * 1000 : 0);
        }
        if (Date.parseFunctions[c] == null) {
            Date.createParser(c);
        }
        var b = Date.parseFunctions[c];
        return Date[b](a);
    };
    Date.createParser = function (format) {
        var funcName = "parse" + Date.parseFunctions.count++;
        var regexNum = Date.parseRegexes.length;
        var currentGroup = 1;
        Date.parseFunctions[format] = funcName;
        var code = "Date." + funcName + " = function(input) {\nvar y = -1, m = -1, d = -1, h = -1, i = -1, s = -1, z = -1;\nvar d = new Date();\ny = d.getFullYear();\nm = d.getMonth();\nd = d.getDate();\nvar results = input.match(Date.parseRegexes[" + regexNum + "]);\nif (results && results.length > 0) {";
        var regex = "";
        var special = false;
        var ch = "";
        for (var i = 0; i < format.length; ++i) {
            ch = format.charAt(i);
            if (!special && ch == "\\") {
                special = true;
            } else {
                if (special) {
                    special = false;
                    regex += String.escape(ch);
                } else {
                    var obj = Date.formatCodeToRegex(ch, currentGroup);
                    currentGroup += obj.g;
                    regex += obj.s;
                    if (obj.g && obj.c) {
                        code += obj.c;
                    }
                }
            }
        }
        code += "if (y > 0 && z > 0){\nvar doyDate = new Date(y,0);\ndoyDate.setDate(z);\nm = doyDate.getMonth();\nd = doyDate.getDate();\n}";
        code += "if (y > 0 && m >= 0 && d > 0 && h >= 0 && i >= 0 && s >= 0)\n{return new Date(y, m, d, h, i, s);}\nelse if (y > 0 && m >= 0 && d > 0 && h >= 0 && i >= 0)\n{return new Date(y, m, d, h, i);}\nelse if (y > 0 && m >= 0 && d > 0 && h >= 0)\n{return new Date(y, m, d, h);}\nelse if (y > 0 && m >= 0 && d > 0)\n{return new Date(y, m, d);}\nelse if (y > 0 && m >= 0)\n{return new Date(y, m);}\nelse if (y > 0)\n{return new Date(y);}\n}return null;}";
        Date.parseRegexes[regexNum] = new RegExp("^" + regex + "$");
        eval(code);
    };
    Date.formatCodeToRegex = function (b, a) {
        switch (b) {
            case"D":
                return {g: 0, c: null, s: "(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)"};
            case"j":
            case"d":
                return {g: 1, c: "d = parseInt(results[" + a + "], 10);\n", s: "(\\d{1,2})"};
            case"l":
                return {g: 0, c: null, s: "(?:" + Date.dayNames.join("|") + ")"};
            case"S":
                return {g: 0, c: null, s: "(?:st|nd|rd|th)"};
            case"w":
                return {g: 0, c: null, s: "\\d"};
            case"z":
                return {g: 1, c: "z = parseInt(results[" + a + "], 10);\n", s: "(\\d{1,3})"};
            case"W":
                return {g: 0, c: null, s: "(?:\\d{2})"};
            case"F":
                return {
                    g: 1,
                    c: "m = parseInt(Date.monthNumbers[results[" + a + "].substring(0, 3)], 10);\n",
                    s: "(" + Date.monthNames.join("|") + ")"
                };
            case"M":
                return {
                    g: 1,
                    c: "m = parseInt(Date.monthNumbers[results[" + a + "]], 10);\n",
                    s: "(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)"
                };
            case"n":
            case"m":
                return {g: 1, c: "m = parseInt(results[" + a + "], 10) - 1;\n", s: "(\\d{1,2})"};
            case"t":
                return {g: 0, c: null, s: "\\d{1,2}"};
            case"L":
                return {g: 0, c: null, s: "(?:1|0)"};
            case"Y":
                return {g: 1, c: "y = parseInt(results[" + a + "], 10);\n", s: "(\\d{4})"};
            case"y":
                return {
                    g: 1,
                    c: "var ty = parseInt(results[" + a + "], 10);\ny = ty > Date.y2kYear ? 1900 + ty : 2000 + ty;\n",
                    s: "(\\d{1,2})"
                };
            case"a":
                return {
                    g: 1,
                    c: "if (results[" + a + "] == 'am') {\nif (h == 12) { h = 0; }\n} else { if (h < 12) { h += 12; }}",
                    s: "(am|pm)"
                };
            case"A":
                return {
                    g: 1,
                    c: "if (results[" + a + "] == 'AM') {\nif (h == 12) { h = 0; }\n} else { if (h < 12) { h += 12; }}",
                    s: "(AM|PM)"
                };
            case"g":
            case"G":
            case"h":
            case"H":
                return {g: 1, c: "h = parseInt(results[" + a + "], 10);\n", s: "(\\d{1,2})"};
            case"i":
                return {g: 1, c: "i = parseInt(results[" + a + "], 10);\n", s: "(\\d{2})"};
            case"s":
                return {g: 1, c: "s = parseInt(results[" + a + "], 10);\n", s: "(\\d{2})"};
            case"O":
                return {g: 0, c: null, s: "[+-]\\d{4}"};
            case"T":
                return {g: 0, c: null, s: "[A-Z]{3}"};
            case"Z":
                return {g: 0, c: null, s: "[+-]\\d{1,5}"};
            default:
                return {g: 0, c: null, s: String.escape(b)};
        }
    };
    Date.prototype.getTimezone = function () {
        return this.toString().replace(/^.*? ([A-Z]{3}) [0-9]{4}.*$/, "$1").replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, "$1$2$3");
    };
    Date.prototype.getGMTOffset = function () {
        return (this.getTimezoneOffset() > 0 ? "-" : "+") + String.leftPad(Math.floor(Math.abs(this.getTimezoneOffset()) / 60), 2, "0") + String.leftPad(Math.abs(this.getTimezoneOffset()) % 60, 2, "0");
    };
    Date.prototype.getDayOfYear = function () {
        var a = 0;
        Date.daysInMonth[1] = this.isLeapYear() ? 29 : 28;
        for (var b = 0; b < this.getMonth(); ++b) {
            a += Date.daysInMonth[b];
        }
        return a + this.getDate();
    };
    Date.prototype.getWeekOfYear = function () {
        var b = this.getDayOfYear() + (4 - this.getDay());
        var a = new Date(this.getFullYear(), 0, 1);
        var c = (7 - a.getDay() + 4);
        return String.leftPad(Math.ceil((b - c) / 7) + 1, 2, "0");
    };
    Date.prototype.isLeapYear = function () {
        var a = this.getFullYear();
        return ((a & 3) == 0 && (a % 100 || (a % 400 == 0 && a)));
    };
    Date.prototype.getFirstDayOfMonth = function () {
        var a = (this.getDay() - (this.getDate() - 1)) % 7;
        return (a < 0) ? (a + 7) : a;
    };
    Date.prototype.getLastDayOfMonth = function () {
        var a = (this.getDay() + (Date.daysInMonth[this.getMonth()] - this.getDate())) % 7;
        return (a < 0) ? (a + 7) : a;
    };
    Date.prototype.getDaysInMonth = function () {
        Date.daysInMonth[1] = this.isLeapYear() ? 29 : 28;
        return Date.daysInMonth[this.getMonth()];
    };
    Date.prototype.getSuffix = function () {
        switch (this.getDate()) {
            case 1:
            case 21:
            case 31:
                return "st";
            case 2:
            case 22:
                return "nd";
            case 3:
            case 23:
                return "rd";
            default:
                return "th";
        }
    };
    String.escape = function (a) {
        return a.replace(/('|\\)/g, "\\$1");
    };
    String.leftPad = function (d, b, c) {
        var a = new String(d);
        if (c == null) {
            c = " ";
        }
        while (a.length < b) {
            a = c + a;
        }
        return a;
    };
    Date.daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    Date.monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
    Date.dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
    Date.y2kYear = 50;
    Date.monthNumbers = {
        Jan: 0,
        Feb: 1,
        Mar: 2,
        Apr: 3,
        May: 4,
        Jun: 5,
        Jul: 6,
        Aug: 7,
        Sep: 8,
        Oct: 9,
        Nov: 10,
        Dec: 11
    };
    Date.patterns = {
        ISO8601LongPattern: "Y-m-d H:i:s",
        ISO8601ShortPattern: "Y-m-d",
        ShortDatePattern: "n/j/Y",
        LongDatePattern: "l, F d, Y",
        FullDateTimePattern: "l, F d, Y g:i:s A",
        MonthDayPattern: "F d",
        ShortTimePattern: "g:i A",
        LongTimePattern: "g:i:s A",
        SortableDateTimePattern: "Y-m-d\\TH:i:s",
        UniversalSortableDateTimePattern: "Y-m-d H:i:sO",
        YearMonthPattern: "F, Y"
    };
});
/**
 * jquery.unique-element-id.js
 *
 * A simple jQuery plugin to get a unique ID for
 * any HTML element
 *
 * Usage:
 *    $('some_element_selector').uid();
 *
 * by Jamie Rumbelow <jamie@jamierumbelow.net>
 * http://jamieonsoftware.com
 * Copyright (c)2011 Jamie Rumbelow
 *
 * Licensed under the MIT license (http://www.opensource.org/licenses/MIT)
 */

N2R('$', function ($) {
    /**
     * Generate a new unqiue ID
     */
    function generateUniqueId(prefix) {

        // Return a unique ID
        return prefix + Math.floor((1 + Math.random()) * 0x1000000000000)
            .toString(16);
    }

    /**
     * Get a unique ID for an element, ensuring that the
     * element has an id="" attribute
     */
    $.fn.uid = function (prefix) {
        var id = null;
        prefix = prefix || "n";
        do {
            id = generateUniqueId(prefix);
        } while ($('#' + id).length > 0);
        return id;
    };

    $.fn.generateUniqueClass = function (prefix) {
        var id = null;
        prefix = prefix || "n";
        do {
            id = generateUniqueId(prefix);
        } while ($('.' + id).length > 0);
        return id;
    };
});
N2D('nUIAutocomplete', ['nUIWidgetBase'], function ($, undefined) {
    "use strict";

    /**
     * @memberOf N2Classes
     *
     * @constructor
     * @augments nUIWidgetBase
     * @this nUIAutocomplete
     */
    function nUIAutocomplete(element, options) {

        this.isRendered = false;

        this.element = $(element);

        this.widgetName = this.widgetName || 'nUIAutocomplete';
        this.widgetEventPrefix = "autocomplete";

        this.isActive = false;
        this.allowBlur = true;

        this.options = $.extend({
            appendTo: 'body',
            source: null,
            select: null,
            positionTo: 'self',
            x: 0,
            y: 0
        }, this.options, options);

        N2Classes.nUIWidgetBase.prototype.constructor.apply(this, arguments);

        this.element.on({
            focus: $.proxy(this.focus, this),
            blur: $.proxy(this.blur, this)
        });

        if (this.options.positionTo === 'self') {
            this.positionTo = this.element;
        } else {
            this.positionTo = this.element.closest(this.options.positionTo);
        }
    }

    nUIAutocomplete.prototype = Object.create(N2Classes.nUIWidgetBase.prototype);
    nUIAutocomplete.prototype.constructor = nUIAutocomplete;

    nUIAutocomplete.prototype.focus = function (e) {
        if (this.isActive === false) {
            this.showList(e);
            this.element.on('click.' + this.widgetEventPrefix, $.proxy(this.showList, this));

            this.isActive = true;
        }
    };

    nUIAutocomplete.prototype.showList = function (e) {
        if (typeof this.options.appendTo === 'function') {
            this.options.appendTo = this.options.appendTo.call(window);
        } else {
            this.options.appendTo = $(this.options.appendTo);
        }
        var $list = this.getList().appendTo(this.options.appendTo);

        var appendToOffset = {
                left: 0,
                top: 0
            },
            offset = this.positionTo.offset();

        if (!this.options.appendTo.is($('body'))) {
            appendToOffset = this.options.appendTo.offset();
            appendToOffset.top -= this.options.appendTo.scrollTop();

            $list.css('height', '');
            var listHeight = $list.height();

            var paneRect = this.options.appendTo[0].getBoundingClientRect(),
                fieldRect = this.positionTo[0].getBoundingClientRect(),
                newListHeight = Math.min(paneRect.top + paneRect.height - fieldRect.top - fieldRect.height - 10, listHeight);

            if (newListHeight < 100 && newListHeight < listHeight) {
                newListHeight = Math.min(fieldRect.top - paneRect.top - 10, listHeight);
                appendToOffset.top = appendToOffset.top + fieldRect.height + newListHeight;
            }

            $list.css('height', newListHeight);
        }
        $list.css({
            left: offset.left - appendToOffset.left + this.element.position().left + this.options.x,
            top: offset.top + this.positionTo.outerHeight() - appendToOffset.top + this.options.y,
            minWidth: this.element.outerWidth(true) + 'px'
        });

        /**
         * If scrollbar dragged with mouse prevent the list to disappear
         */
        $list.off('.' + this.widgetEventPrefix)
            .on('mousedown.' + this.widgetEventPrefix, $.proxy(function (e) {
                if ($(e.target).is($list)) {
                    this.element.parent().addClass('focus2');
                    this.allowBlur = false;
                }
            }, this))
            .on('mouseup.' + this.widgetEventPrefix, $.proxy(function (e) {
                if ($(e.target).is($list)) {
                    this.allowBlur = true;
                    this.element.focus();
                    this.element.parent().removeClass('focus2');
                }
            }, this));
    };

    nUIAutocomplete.prototype.blur = function (e) {
        if (this.allowBlur) {
            this.hide();
            this.element.off('.' + this.widgetEventPrefix);
        }
    };

    nUIAutocomplete.prototype.hide = function () {
        this.$list.detach();
        this.isActive = false;
    };

    nUIAutocomplete.prototype.getList = function () {
        if (!this.isRendered) {
            this.$list = $('<ul class="n2 nui-autocomplete"></ul>')
                .attr({
                    "unselectable": "on"
                })
                .on({
                    mousedown: $.proxy(N2Classes.WindowManager.setMouseDownArea, null, 'nUIAutocomplete'),
                    'wheel': function (e) {
                        e.stopPropagation();
                    }
                });
            var options = this.options.source.call(this, this.ui());
            for (var i = 0; i < options.length; i++) {
                $('<li class="nui-menu-item"><div tabindex="-1">' + options[i] + '</div></li>')
                    .on({
                        mousedown: function (e) {
                            e.preventDefault();
                        },
                        click: $.proxy(function (value, e) {
                            this._trigger('select', e, {
                                value: value
                            });
                            this.hide();
                        }, this, options[i])
                    })
                    .appendTo(this.$list);
            }
            this.isRendered = true;
        }

        return this.$list;
    };

    nUIAutocomplete.prototype.ui = function () {
        return {};
    };

    N2Classes.nUIWidgetBase.register('nUIAutocomplete');

    return nUIAutocomplete;
});
N2D('nUIDraggableBar', ['nUIMouse'], function ($, undefined) {
    "use strict";

    /**
     * @memberOf N2Classes
     *
     * @param element
     * @param options
     */
    function nUIDraggableBar(element, options) {
        this.element = $(element);

        this.widgetName = this.widgetName || 'nUIDraggable';
        this.widgetEventPrefix = "drag";

        this.options = $.extend({
            // Callbacks
            drag: null,
            start: null,
            stop: null
        }, this.options, options);

        N2Classes.nUIMouse.prototype.constructor.apply(this, arguments);

        this._mouseInit();
    }

    nUIDraggableBar.prototype = Object.create(N2Classes.nUIMouse.prototype);
    nUIDraggableBar.prototype.constructor = nUIDraggableBar;

    nUIDraggableBar.prototype._mouseStart = function (event) {

        this.currentData = this.originalData = {
            margin: parseInt(this.element.css(n2const.rtl.marginLeft))
        };
        this.originalMousePosition = {left: event.pageX};

        this.element.addClass("nui-draggable-dragging");

        this._trigger("start", event, this.ui());

        this._mouseDrag(event);
        return true;
    };

    nUIDraggableBar.prototype._mouseDrag = function (event) {
        var dx = (event.pageX - this.originalMousePosition.left) || 0;
        this.currentData = {};

        if (!n2const.rtl.isRtl) {
            this.currentData.margin = Math.max(0, this.originalData.margin + dx);
        } else {
            this.currentData.margin = Math.max(0, this.originalData.margin - dx);
        }

        this._trigger("drag", event, this.ui());

        this.element.css(n2const.rtl.marginLeft, this.currentData.margin);

        return true;
    };


    nUIDraggableBar.prototype._mouseStop = function (event) {

        this._trigger("stop", event, this.ui());

        return true;
    };

    nUIDraggableBar.prototype.ui = function () {
        return {
            currentData: this.currentData
        };
    };

    N2Classes.nUIWidgetBase.register('nUIDraggableBar');

    return nUIDraggableBar;
});
N2D('nUIDraggableDelay', ['nUIMouse'], function ($, undefined) {
    "use strict";

    /**
     * @memberOf N2Classes
     *
     * @param element
     * @param options
     */
    function nUIDraggableDelay(element, options) {
        this.element = $(element);

        this.widgetName = this.widgetName || 'nUIDraggable';
        this.widgetEventPrefix = "drag";

        this.options = $.extend({
            // Callbacks
            drag: null,
            start: null,
            stop: null
        }, this.options, options);

        N2Classes.nUIMouse.prototype.constructor.apply(this, arguments);

        this._mouseInit();
    }

    nUIDraggableDelay.prototype = Object.create(N2Classes.nUIMouse.prototype);
    nUIDraggableDelay.prototype.constructor = nUIDraggableDelay;

    nUIDraggableDelay.prototype._mouseStart = function (event) {

        this.currentData = this.originalData = {
            width: parseInt(this.element.width())
        };
        this.originalMousePosition = {left: event.pageX};

        this.element.addClass("nui-draggable-dragging");

        this._trigger("start", event, this.ui());

        this._mouseDrag(event);
        return true;
    };

    nUIDraggableDelay.prototype._mouseDrag = function (event) {
        var dx = (event.pageX - this.originalMousePosition.left) || 0;
        this.currentData = {};

        if (!n2const.rtl.isRtl) {
            this.currentData.width = Math.max(0, this.originalData.width + dx);
        } else {
            this.currentData.width = Math.max(0, this.originalData.width - dx);
        }

        this._trigger("drag", event, this.ui());

        this.element.width(this.currentData.width);

        return true;
    };


    nUIDraggableDelay.prototype._mouseStop = function (event) {

        this._trigger("stop", event, this.ui());

        return true;
    };

    nUIDraggableDelay.prototype.ui = function () {
        return {
            currentData: this.currentData
        };
    };

    N2Classes.nUIWidgetBase.register('nUIDraggableDelay');

    return nUIDraggableDelay;
});
N2D('nUIDraggable', ['nUIMouse'], function ($, undefined) {
    "use strict";

    /**
     * @memberOf N2Classes
     *
     * @constructor
     * @augments nUIMouse
     * @this nUIDraggable
     */
    function nUIDraggable(element, options) {
        this.element = $(element);

        this.widgetName = this.widgetName || 'nUIDraggable';
        this.widgetEventPrefix = "drag";

        this.options = $.extend({
            addClasses: true,
            appendTo: "parent",
            axis: false,
            containment: false,
            cursor: "auto",
            cursorAt: false,
            handle: false,
            helper: "original",
            scroll: true,
            scrollSensitivity: 20,
            scrollSpeed: 20,

            // Callbacks
            drag: null,
            start: null,
            stop: null
        }, this.options, options);

        N2Classes.nUIMouse.prototype.constructor.apply(this, arguments);

        this.create();
    }

    nUIDraggable.prototype = Object.create(N2Classes.nUIMouse.prototype);
    nUIDraggable.prototype.constructor = nUIDraggable;

    nUIDraggable.prototype.create = function () {

        if (this.options.helper === "original") {
            this._setPositionRelative();
        }
        if (this.options.addClasses) {
            this.element.addClass("nui-draggable");
        }
        this._setHandleClassName();

        this._mouseInit();
    };

    nUIDraggable.prototype._setPositionRelative = function () {
        if (!( /^(?:r|a|f)/ ).test(this.element.css("position"))) {
            this.element[0].style.position = "relative";
        }
    };

    nUIDraggable.prototype._getHandle = function (event) {
        return this.options.handle ?
            !!$(event.target).closest(this.element.find(this.options.handle)).length :
            true;
    };

    nUIDraggable.prototype._setHandleClassName = function () {
        this.handleElement = this.options.handle ?
            this.element.find(this.options.handle) : this.element;
        this.handleElement.addClass("nui-draggable-handle");
    };

    nUIDraggable.prototype._mouseCapture = function (event) {
        var o = this.options;
        // Among others, prevent a drag on a resizable-handle
        if (this.helper || o.disabled ||
            $(event.target).closest(".nui-resizable-handle").length > 0) {
            return false;
        }

        //Quit if we're not on a valid handle
        this.handle = this._getHandle(event);
        if (!this.handle) {
            return false;
        }

        this._blurActiveElement(event);

        return true;
    };

    nUIDraggable.prototype.cancel = function () {

        if (this.helper.is(".nui-draggable-dragging")) {
            this._mouseUp(new $.Event("mouseup", {target: this.element[0]}));
        } else {
            this._clear();
        }

        return this;

    };

    $.fn.nuiScrollParent = function (includeHidden) {
        var position = this.css("position"),
            excludeStaticParent = position === "absolute",
            overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/,
            scrollParent = this.parents().filter(function () {
                var parent = $(this);
                if (excludeStaticParent && parent.css("position") === "static") {
                    return false;
                }
                return overflowRegex.test(parent.css("overflow") + parent.css("overflow-y") +
                    parent.css("overflow-x"));
            }).eq(0);

        return position === "fixed" || !scrollParent.length ?
            $(this[0].ownerDocument || document) :
            scrollParent;
    };

    nUIDraggable.prototype._mouseStart = function (event) {
        var o = this.options;
        //Create and append the visible helper
        this.helper = this._createHelper(event);

        this.helper.addClass("nui-draggable-dragging");

        //Cache the helper size
        this._cacheHelperProportions();

        /*
         * - Position generation -
         * This block generates everything position related - it's the core of draggables.
         */

        //Cache the margins of the original element
        this._cacheMargins();

        //Store the helper's css position
        this.cssPosition = this.helper.css("position");
        this.scrollParent = this.helper.nuiScrollParent(true);
        this.offsetParent = this.helper.offsetParent();
        this.hasFixedAncestor = this.helper.parents().filter(function () {
                return $(this).css("position") === "fixed";
            }).length > 0;

        //The element's absolute position on the page minus margins
        this.positionAbs = this.element.offset();
        this._refreshOffsets(event);

        //Generate the original position
        this.originalPosition = this.position = this._generatePosition(event, false);
        this.originalPageX = event.pageX;
        this.originalPageY = event.pageY;

        //Adjust the mouse offset relative to the helper if "cursorAt" is supplied
        ( o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt) );

        //Set a containment if given in the options
        this._setContainment();

        //Trigger event + callbacks
        if (this._trigger("start", event) === false) {
            this._clear();
            return false;
        }

        //Recache the helper size
        this._cacheHelperProportions();


        // Execute the drag once - this causes the helper not to be visible before getting its
        // correct position
        this._mouseDrag(event, true);

        return true;
    };

    nUIDraggable.prototype._mouseDrag = function (event, noPropagation) {

        // reset any necessary cached properties (see #5009)
        if (this.hasFixedAncestor) {
            this.offset.parent = this._getParentOffset();
        }

        //Compute the helpers position
        this.position = this._generatePosition(event, true);
        this.positionAbs = this._convertPositionTo("absolute");

        //Call plugins and callbacks and use the resulting position if something is returned
        if (!noPropagation) {
            var ui = this._uiHash();
            if (this._trigger("drag", event, ui) === false) {
                this._mouseUp(new $.Event("mouseup", event));
                return false;
            }
            this.position = ui.position;
        }

        this.helper[0].style.left = this.position.left + "px";
        this.helper[0].style.top = this.position.top + "px";

        return false;
    };

    nUIDraggable.prototype._mouseStop = function (event) {

        if (this._trigger("stop", event) !== false) {
            this._clear();
        }

        return false;
    };

    nUIDraggable.prototype._mouseUp = function (event) {

        // Only need to focus if the event occurred on the draggable itself, see #10527
        if (this.handleElement.is(event.target)) {

            // The interaction is over; whether or not the click resulted in a drag,
            // focus the element
            this.element.trigger("focus");
        }

        return N2Classes.nUIMouse.prototype._mouseUp.call(this, event);
    };

    nUIDraggable.prototype._trigger = function (type, event, ui) {
        ui = ui || this._uiHash();

        return N2Classes.nUIWidgetBase.prototype._trigger.call(this, type, event, ui);
    };

    nUIDraggable.prototype._uiHash = function () {
        return {
            helper: this.helper,
            position: this.position,
            originalPosition: this.originalPosition
        };
    };

    nUIDraggable.prototype._createHelper = function (event) {

        var o = this.options,
            helperIsFunction = $.isFunction(o.helper),
            helper = helperIsFunction ?
                $(o.helper.apply(this.element[0], [event])) :
                ( o.helper === "clone" ?
                    this.element.clone().removeAttr("id") :
                    this.element );

        if (!helper.parents("body").length) {
            helper.appendTo(( o.appendTo === "parent" ?
                this.element[0].parentNode :
                o.appendTo ));
        }

        // Http://bugs.jqueryui.com/ticket/9446
        // a helper function can return the original element
        // which wouldn't have been set to relative in _create
        if (helperIsFunction && helper[0] === this.element[0]) {
            this._setPositionRelative();
        }

        if (helper[0] !== this.element[0] && !( /(fixed|absolute)/ ).test(helper.css("position"))) {
            helper.css("position", "absolute");
        }

        return helper;

    };

    nUIDraggable.prototype._cacheHelperProportions = function () {
        this.helperProportions = {
            width: this.helper.outerWidth(),
            height: this.helper.outerHeight()
        };
    };

    nUIDraggable.prototype._cacheMargins = function () {
        this.margins = {
            left: ( parseInt(this.element.css("marginLeft"), 10) || 0 ),
            top: ( parseInt(this.element.css("marginTop"), 10) || 0 ),
            right: ( parseInt(this.element.css("marginRight"), 10) || 0 ),
            bottom: ( parseInt(this.element.css("marginBottom"), 10) || 0 )
        };
    };

    nUIDraggable.prototype._refreshOffsets = function (event) {
        this.offset = {
            top: this.positionAbs.top - this.margins.top,
            left: this.positionAbs.left - this.margins.left,
            scroll: false,
            parent: this._getParentOffset(),
            relative: this._getRelativeOffset()
        };

        this.offset.click = {
            left: event.pageX - this.offset.left,
            top: event.pageY - this.offset.top
        };
    };

    nUIDraggable.prototype._getParentOffset = function () {

        //Get the offsetParent and cache its position
        var po = this.offsetParent.offset(),
            document = this.document[0];

        // This is a special case where we need to modify a offset calculated on start, since the
        // following happened:
        // 1. The position of the helper is absolute, so it's position is calculated based on the
        // next positioned parent
        // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't
        // the document, which means that the scroll is included in the initial calculation of the
        // offset of the parent, and never recalculated upon drag
        if (this.cssPosition === "absolute" && this.scrollParent[0] !== document &&
            $.contains(this.scrollParent[0], this.offsetParent[0])) {
            po.left += this.scrollParent.scrollLeft();
            po.top += this.scrollParent.scrollTop();
        }

        if (this._isRootNode(this.offsetParent[0])) {
            po = {top: 0, left: 0};
        }

        return {
            top: po.top + ( parseInt(this.offsetParent.css("borderTopWidth"), 10) || 0 ),
            left: po.left + ( parseInt(this.offsetParent.css("borderLeftWidth"), 10) || 0 )
        };

    };

    nUIDraggable.prototype._getRelativeOffset = function () {
        if (this.cssPosition !== "relative") {
            return {top: 0, left: 0};
        }

        var p = this.element.position(),
            scrollIsRootNode = this._isRootNode(this.scrollParent[0]);

        return {
            top: p.top - ( parseInt(this.helper.css("top"), 10) || 0 ) +
            ( !scrollIsRootNode ? this.scrollParent.scrollTop() : 0 ),
            left: p.left - ( parseInt(this.helper.css("left"), 10) || 0 ) +
            ( !scrollIsRootNode ? this.scrollParent.scrollLeft() : 0 )
        };

    };

    nUIDraggable.prototype._convertPositionTo = function (d, pos) {

        if (!pos) {
            pos = this.position;
        }

        var mod = d === "absolute" ? 1 : -1,
            scrollIsRootNode = this._isRootNode(this.scrollParent[0]);

        return {
            top: (

                // The absolute mouse position
                pos.top +

                // Only for relative positioned nodes: Relative offset from element to offset parent
                this.offset.relative.top * mod +

                // The offsetParent's offset without borders (offset + border)
                this.offset.parent.top * mod -
                ( ( this.cssPosition === "fixed" ?
                    -this.offset.scroll.top :
                    ( scrollIsRootNode ? 0 : this.offset.scroll.top ) ) * mod )
            ),
            left: (

                // The absolute mouse position
                pos.left +

                // Only for relative positioned nodes: Relative offset from element to offset parent
                this.offset.relative.left * mod +

                // The offsetParent's offset without borders (offset + border)
                this.offset.parent.left * mod -
                ( ( this.cssPosition === "fixed" ?
                    -this.offset.scroll.left :
                    ( scrollIsRootNode ? 0 : this.offset.scroll.left ) ) * mod )
            )
        };

    };

    nUIDraggable.prototype._setContainment = function () {

        var isUserScrollable, c, ce,
            o = this.options,
            document = this.document[0];

        this.relativeContainer = null;

        if (!o.containment) {
            this.containment = null;
            return;
        }

        if (o.containment === "window") {
            this.containment = [
                $(window).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
                $(window).scrollTop() - this.offset.relative.top - this.offset.parent.top,
                $(window).scrollLeft() + $(window).width() -
                this.helperProportions.width - this.margins.left,
                $(window).scrollTop() +
                ( $(window).height() || document.body.parentNode.scrollHeight ) -
                this.helperProportions.height - this.margins.top
            ];
            return;
        }

        if (o.containment === "document") {
            this.containment = [
                0,
                0,
                $(document).width() - this.helperProportions.width - this.margins.left,
                ( $(document).height() || document.body.parentNode.scrollHeight ) -
                this.helperProportions.height - this.margins.top
            ];
            return;
        }

        if (o.containment.constructor === Array) {
            this.containment = o.containment;
            return;
        }

        if (o.containment === "parent") {
            o.containment = this.helper[0].parentNode;
        }

        c = $(o.containment);
        ce = c[0];

        if (!ce) {
            return;
        }

        isUserScrollable = /(scroll|auto)/.test(c.css("overflow"));

        this.containment = [
            ( parseInt(c.css("borderLeftWidth"), 10) || 0 ) +
            ( parseInt(c.css("paddingLeft"), 10) || 0 ),
            ( parseInt(c.css("borderTopWidth"), 10) || 0 ) +
            ( parseInt(c.css("paddingTop"), 10) || 0 ),
            ( isUserScrollable ? Math.max(ce.scrollWidth, ce.offsetWidth) : ce.offsetWidth ) -
            ( parseInt(c.css("borderRightWidth"), 10) || 0 ) -
            ( parseInt(c.css("paddingRight"), 10) || 0 ) -
            this.helperProportions.width -
            this.margins.left -
            this.margins.right,
            ( isUserScrollable ? Math.max(ce.scrollHeight, ce.offsetHeight) : ce.offsetHeight ) -
            ( parseInt(c.css("borderBottomWidth"), 10) || 0 ) -
            ( parseInt(c.css("paddingBottom"), 10) || 0 ) -
            this.helperProportions.height -
            this.margins.top -
            this.margins.bottom
        ];
        this.relativeContainer = c;
    };


    nUIDraggable.prototype._adjustOffsetFromHelper = function (obj) {
        if (typeof obj === "string") {
            obj = obj.split(" ");
        }
        if ($.isArray(obj)) {
            obj = {left: +obj[0], top: +obj[1] || 0};
        }
        if ("left" in obj) {
            this.offset.click.left = obj.left + this.margins.left;
        }
        if ("right" in obj) {
            this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
        }
        if ("top" in obj) {
            this.offset.click.top = obj.top + this.margins.top;
        }
        if ("bottom" in obj) {
            this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
        }
    };

    nUIDraggable.prototype._isRootNode = function (element) {
        return ( /(html|body)/i ).test(element.tagName) || element === this.document[0];
    };

    nUIDraggable.prototype._generatePosition = function (event, constrainPosition) {

        var containment, co,
            o = this.options,
            scrollIsRootNode = this._isRootNode(this.scrollParent[0]),
            pageX = event.pageX,
            pageY = event.pageY;

        // Cache the scroll
        if (!scrollIsRootNode || !this.offset.scroll) {
            this.offset.scroll = {
                top: this.scrollParent.scrollTop(),
                left: this.scrollParent.scrollLeft()
            };
        }

        /*
         * - Position constraining -
         * Constrain the position to containment.
         */

        // If we are not dragging yet, we won't check for options
        if (constrainPosition) {
            if (this.containment) {
                if (this.relativeContainer) {
                    co = this.relativeContainer.offset();
                    containment = [
                        this.containment[0] + co.left,
                        this.containment[1] + co.top,
                        this.containment[2] + co.left,
                        this.containment[3] + co.top
                    ];
                } else {
                    containment = this.containment;
                }

                if (event.pageX - this.offset.click.left < containment[0]) {
                    pageX = containment[0] + this.offset.click.left;
                }
                if (event.pageY - this.offset.click.top < containment[1]) {
                    pageY = containment[1] + this.offset.click.top;
                }
                if (event.pageX - this.offset.click.left > containment[2]) {
                    pageX = containment[2] + this.offset.click.left;
                }
                if (event.pageY - this.offset.click.top > containment[3]) {
                    pageY = containment[3] + this.offset.click.top;
                }
            }

            if (o.axis === "y") {
                pageX = this.originalPageX;
            }

            if (o.axis === "x") {
                pageY = this.originalPageY;
            }
        }

        return {
            top: (

                // The absolute mouse position
                pageY -

                // Click offset (relative to the element)
                this.offset.click.top -

                // Only for relative positioned nodes: Relative offset from element to offset parent
                this.offset.relative.top -

                // The offsetParent's offset without borders (offset + border)
                this.offset.parent.top +
                ( this.cssPosition === "fixed" ?
                    -this.offset.scroll.top :
                    ( scrollIsRootNode ? 0 : this.offset.scroll.top ) )
            ),
            left: (

                // The absolute mouse position
                pageX -

                // Click offset (relative to the element)
                this.offset.click.left -

                // Only for relative positioned nodes: Relative offset from element to offset parent
                this.offset.relative.left -

                // The offsetParent's offset without borders (offset + border)
                this.offset.parent.left +
                ( this.cssPosition === "fixed" ?
                    -this.offset.scroll.left :
                    ( scrollIsRootNode ? 0 : this.offset.scroll.left ) )
            )
        };

    };

    nUIDraggable.prototype._clear = function () {
        this.helper.removeClass("nui-draggable-dragging");
        if (this.helper[0] !== this.element[0] && !this.cancelHelperRemoval) {
            this.helper.remove();
        }
        this.helper = null;
        this.cancelHelperRemoval = false;
        if (this.destroyOnClear) {
            this.destroy();
        }
    };


    var safeActiveElement = function (document) {
            var activeElement;

            // Support: IE 9 only
            // IE9 throws an "Unspecified error" accessing document.activeElement from an <iframe>
            try {
                activeElement = document.activeElement;
            } catch (error) {
                activeElement = document.body;
            }

            // Support: IE 9 - 11 only
            // IE may return null instead of an element
            // Interestingly, this only seems to occur when NOT in an iframe
            if (!activeElement) {
                activeElement = document.body;
            }

            // Support: IE 11 only
            // IE11 returns a seemingly empty object in some cases when accessing
            // document.activeElement from an <iframe>
            if (!activeElement.nodeName) {
                activeElement = document.body;
            }

            return activeElement;
        },
        safeBlur = function (element) {

            // Support: IE9 - 10 only
            // If the <body> is blurred, IE will switch windows, see #9420
            if (element && element.nodeName.toLowerCase() !== "body") {
                $(element).trigger("blur");
            }
        };

    nUIDraggable.prototype._blurActiveElement = function (event) {
        var activeElement = safeActiveElement(this.document[0]),
            target = $(event.target);

        // Don't blur if the event occurred on an element that is within
        // the currently focused element
        // See #10527, #12472
        if (target.closest(activeElement).length) {
            return;
        }

        // Blur any element that currently has focus, see #4261
        safeBlur(activeElement);
    };

    N2Classes.nUIWidgetBase.register('nUIDraggable');

    return nUIDraggable;
});
/*
 * jQuery File Upload Plugin 5.42.3
 * https://github.com/blueimp/jQuery-File-Upload
 *
 * Copyright 2010, Sebastian Tschan
 * https://blueimp.net
 *
 * Licensed under the MIT license:
 * http://www.opensource.org/licenses/MIT
 */

/* jshint nomen:false */
/* global define, require, window, document, location, Blob, FormData */

N2D('nUIFileUpload', ["nUIWidgetBase"], function ($, undefined) {
    'use strict';

    // Detect file input support, based on
    // http://viljamis.com/blog/2012/file-upload-support-on-mobile/
    $.support.fileInput = !(new RegExp(
        // Handle devices which give false positives for the feature detection:
        '(Android (1\\.[0156]|2\\.[01]))' +
        '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' +
        '|(w(eb)?OSBrowser)|(webOS)' +
        '|(Kindle/(1\\.0|2\\.[05]|3\\.0))'
        ).test(window.navigator.userAgent) ||
        // Feature detection for all other devices:
        $('<input type="file">').prop('disabled'));

    // The FileReader API is not actually used, but works as feature detection,
    // as some Safari versions (5?) support XHR file uploads via the FormData API,
    // but not non-multipart XHR file uploads.
    // window.XMLHttpRequestUpload is not available on IE10, so we check for
    // window.ProgressEvent instead to detect XHR2 file upload capability:
    $.support.xhrFileUpload = !!(window.ProgressEvent && window.FileReader);
    $.support.xhrFormDataFileUpload = !!window.FormData;

    // Detect support for Blob slicing (required for chunked uploads):
    $.support.blobSlice = window.Blob && (Blob.prototype.slice ||
        Blob.prototype.webkitSlice || Blob.prototype.mozSlice);

    // Helper function to create drag handlers for dragover/dragenter/dragleave:
    function getDragHandler(type) {
        var isDragOver = type === 'dragover';
        return function (e) {
            e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
            var dataTransfer = e.dataTransfer;
            if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 &&
                this._trigger(
                    type,
                    $.Event(type, {delegatedEvent: e})
                ) !== false) {
                e.preventDefault();
                if (isDragOver) {
                    dataTransfer.dropEffect = 'copy';
                }
            }
        };
    }

    // The fileupload widget listens for change events on file input fields defined
    // via fileInput setting and paste or drop events of the given dropZone.
    // In addition to the default jQuery Widget methods, the fileupload widget
    // exposes the "add" and "send" methods, to add or directly send files using
    // the fileupload API.
    // By default, files added via file input selection, paste, drag & drop or
    // "add" method are uploaded immediately, but it is possible to override
    // the "add" callback option to queue file uploads.


    /**
     * @class
     * @constructor
     * @augments nUIWidgetBase

     * @this nUIFileUpload
     */
    function nUIFileUpload(element, options) {

        this.element = $(element);

        this.widgetName = this.widgetName || 'nUIFileUpload';

        this.options = $.extend({
            // The drop target element(s), by the default the complete document.
            // Set to null to disable drag & drop support:
            dropZone: $(document),
            // The paste target element(s), by the default undefined.
            // Set to a DOM node or jQuery object to enable file pasting:
            pasteZone: undefined,
            // The file input field(s), that are listened to for change events.
            // If undefined, it is set to the file input fields inside
            // of the widget element on plugin initialization.
            // Set to null to disable the change listener.
            fileInput: undefined,
            // By default, the file input field is replaced with a clone after
            // each input field change event. This is required for iframe transport
            // queues and allows change events to be fired for the same file
            // selection, but can be disabled by setting the following option to false:
            replaceFileInput: true,
            // The parameter name for the file form data (the request argument name).
            // If undefined or empty, the name property of the file input field is
            // used, or "files[]" if the file input name property is also empty,
            // can be a string or an array of strings:
            paramName: undefined,
            // By default, each file of a selection is uploaded using an individual
            // request for XHR type uploads. Set to false to upload file
            // selections in one request each:
            singleFileUploads: true,
            // To limit the number of files uploaded with one XHR request,
            // set the following option to an integer greater than 0:
            limitMultiFileUploads: undefined,
            // The following option limits the number of files uploaded with one
            // XHR request to keep the request size under or equal to the defined
            // limit in bytes:
            limitMultiFileUploadSize: undefined,
            // Multipart file uploads add a number of bytes to each uploaded file,
            // therefore the following option adds an overhead for each file used
            // in the limitMultiFileUploadSize configuration:
            limitMultiFileUploadSizeOverhead: 512,
            // Set the following option to true to issue all file upload requests
            // in a sequential order:
            sequentialUploads: false,
            // To limit the number of concurrent uploads,
            // set the following option to an integer greater than 0:
            limitConcurrentUploads: undefined,
            // Set the following option to true to force iframe transport uploads:
            forceIframeTransport: false,
            // Set the following option to the location of a redirect url on the
            // origin server, for cross-domain iframe transport uploads:
            redirect: undefined,
            // The parameter name for the redirect url, sent as part of the form
            // data and set to 'redirect' if this option is empty:
            redirectParamName: undefined,
            // Set the following option to the location of a postMessage window,
            // to enable postMessage transport uploads:
            postMessage: undefined,
            // By default, XHR file uploads are sent as multipart/form-data.
            // The iframe transport is always using multipart/form-data.
            // Set to false to enable non-multipart XHR uploads:
            multipart: true,
            // To upload large files in smaller chunks, set the following option
            // to a preferred maximum chunk size. If set to 0, null or undefined,
            // or the browser does not support the required Blob API, files will
            // be uploaded as a whole.
            maxChunkSize: undefined,
            // When a non-multipart upload or a chunked multipart upload has been
            // aborted, this option can be used to resume the upload by setting
            // it to the size of the already uploaded bytes. This option is most
            // useful when modifying the options object inside of the "add" or
            // "send" callbacks, as the options are cloned for each file upload.
            uploadedBytes: undefined,
            // By default, failed (abort or error) file uploads are removed from the
            // global progress calculation. Set the following option to false to
            // prevent recalculating the global progress data:
            recalculateProgress: true,
            // Interval in milliseconds to calculate and trigger progress events:
            progressInterval: 100,
            // Interval in milliseconds to calculate progress bitrate:
            bitrateInterval: 500,
            // By default, uploads are started automatically when adding files:
            autoUpload: true,

            // Error and info messages:
            messages: {
                uploadedBytes: 'Uploaded bytes exceed file size'
            },

            // Translation function, gets the message key to be translated
            // and an object with context specific data as arguments:
            i18n: function (message, context) {
                message = this.messages[message] || message.toString();
                if (context) {
                    $.each(context, function (key, value) {
                        message = message.replace('{' + key + '}', value);
                    });
                }
                return message;
            },

            // Additional form data to be sent along with the file uploads can be set
            // using this option, which accepts an array of objects with name and
            // value properties, a function returning such an array, a FormData
            // object (for XHR file uploads), or a simple object.
            // The form of the first fileInput is given as parameter to the function:
            formData: function (form) {
                return form.serializeArray();
            },

            // The add callback is invoked as soon as files are added to the fileupload
            // widget (via file input selection, drag & drop, paste or add API call).
            // If the singleFileUploads option is enabled, this callback will be
            // called once for each file in the selection for XHR file uploads, else
            // once for each file selection.
            //
            // The upload starts when the submit method is invoked on the data parameter.
            // The data object contains a files property holding the added files
            // and allows you to override plugin options as well as define ajax settings.
            //
            // Listeners for this callback can also be bound the following way:
            // .bind('fileuploadadd', func);
            //
            // data.submit() returns a Promise object and allows to attach additional
            // handlers using jQuery's Deferred callbacks:
            // data.submit().done(func).fail(func).always(func);
            add: function (e, data) {
                if (e.isDefaultPrevented()) {
                    return false;
                }
                if (data.autoUpload || (data.autoUpload !== false &&
                    $(this).nUIFileUpload('option', 'autoUpload'))) {
                    data.process().done(function () {
                        data.submit();
                    });
                }
            },

            // Other callbacks:

            // Callback for the submit event of each file upload:
            // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);

            // Callback for the start of each file upload request:
            // send: function (e, data) {}, // .bind('fileuploadsend', func);

            // Callback for successful uploads:
            // done: function (e, data) {}, // .bind('fileuploaddone', func);

            // Callback for failed (abort or error) uploads:
            // fail: function (e, data) {}, // .bind('fileuploadfail', func);

            // Callback for completed (success, abort or error) requests:
            // always: function (e, data) {}, // .bind('fileuploadalways', func);

            // Callback for upload progress events:
            // progress: function (e, data) {}, // .bind('fileuploadprogress', func);

            // Callback for global upload progress events:
            // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);

            // Callback for uploads start, equivalent to the global ajaxStart event:
            // start: function (e) {}, // .bind('fileuploadstart', func);

            // Callback for uploads stop, equivalent to the global ajaxStop event:
            // stop: function (e) {}, // .bind('fileuploadstop', func);

            // Callback for change events of the fileInput(s):
            // change: function (e, data) {}, // .bind('fileuploadchange', func);

            // Callback for paste events to the pasteZone(s):
            // paste: function (e, data) {}, // .bind('fileuploadpaste', func);

            // Callback for drop events of the dropZone(s):
            // drop: function (e, data) {}, // .bind('fileuploaddrop', func);

            // Callback for dragover events of the dropZone(s):
            // dragover: function (e) {}, // .bind('fileuploaddragover', func);

            // Callback for the start of each chunk upload request:
            // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);

            // Callback for successful chunk uploads:
            // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);

            // Callback for failed (abort or error) chunk uploads:
            // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);

            // Callback for completed (success, abort or error) chunk upload requests:
            // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);

            // The plugin options are used as settings object for the ajax calls.
            // The following are jQuery ajax settings required for the file uploads:
            processData: false,
            contentType: false,
            cache: false
        }, this.options, options);

        N2Classes.nUIWidgetBase.prototype.constructor.apply(this, arguments);

        this._specialOptions = [
            'fileInput',
            'dropZone',
            'pasteZone',
            'multipart',
            'forceIframeTransport'
        ];

        this.create();
    }

    nUIFileUpload.prototype = Object.create(N2Classes.nUIWidgetBase.prototype);
    nUIFileUpload.prototype.constructor = nUIFileUpload;


    nUIFileUpload.prototype._blobSlice = $.support.blobSlice && function () {
        var slice = this.slice || this.webkitSlice || this.mozSlice;
        return slice.apply(this, arguments);
    };

    nUIFileUpload.prototype._BitrateTimer = function () {
        this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
        this.loaded = 0;
        this.bitrate = 0;
        this.getBitrate = function (now, loaded, interval) {
            var timeDiff = now - this.timestamp;
            if (!this.bitrate || !interval || timeDiff > interval) {
                this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
                this.loaded = loaded;
                this.timestamp = now;
            }
            return this.bitrate;
        };
    };

    nUIFileUpload.prototype._isXHRUpload = function (options) {
        return !options.forceIframeTransport &&
            ((!options.multipart && $.support.xhrFileUpload) ||
                $.support.xhrFormDataFileUpload);
    };

    nUIFileUpload.prototype._getFormData = function (options) {
        var formData;
        if ($.type(options.formData) === 'function') {
            return options.formData(options.form);
        }
        if ($.isArray(options.formData)) {
            return options.formData;
        }
        if ($.type(options.formData) === 'object') {
            formData = [];
            $.each(options.formData, function (name, value) {
                formData.push({name: name, value: value});
            });
            return formData;
        }
        return [];
    };

    nUIFileUpload.prototype._getTotal = function (files) {
        var total = 0;
        $.each(files, function (index, file) {
            total += file.size || 1;
        });
        return total;
    };

    nUIFileUpload.prototype._initProgressObject = function (obj) {
        var progress = {
            loaded: 0,
            total: 0,
            bitrate: 0
        };
        if (obj._progress) {
            $.extend(obj._progress, progress);
        } else {
            obj._progress = progress;
        }
    };

    nUIFileUpload.prototype._initResponseObject = function (obj) {
        var prop;
        if (obj._response) {
            for (prop in obj._response) {
                if (obj._response.hasOwnProperty(prop)) {
                    delete obj._response[prop];
                }
            }
        } else {
            obj._response = {};
        }
    };

    nUIFileUpload.prototype._onProgress = function (e, data) {
        if (e.lengthComputable) {
            var now = ((Date.now) ? Date.now() : (new Date()).getTime()),
                loaded;
            if (data._time && data.progressInterval &&
                (now - data._time < data.progressInterval) &&
                e.loaded !== e.total) {
                return;
            }
            data._time = now;
            loaded = Math.floor(
                e.loaded / e.total * (data.chunkSize || data._progress.total)
            ) + (data.uploadedBytes || 0);
            // Add the difference from the previously loaded state
            // to the global loaded counter:
            this._progress.loaded += (loaded - data._progress.loaded);
            this._progress.bitrate = this._bitrateTimer.getBitrate(
                now,
                this._progress.loaded,
                data.bitrateInterval
            );
            data._progress.loaded = data.loaded = loaded;
            data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
                now,
                loaded,
                data.bitrateInterval
            );
            // Trigger a custom progress event with a total data property set
            // to the file size(s) of the current upload and a loaded data
            // property calculated accordingly:
            this._trigger(
                'progress',
                $.Event('progress', {delegatedEvent: e}),
                data
            );
            // Trigger a global progress event for all current file uploads,
            // including ajax calls queued for sequential file uploads:
            this._trigger(
                'progressall',
                $.Event('progressall', {delegatedEvent: e}),
                this._progress
            );
        }
    };

    nUIFileUpload.prototype._initProgressListener = function (options) {
        var that = this,
            xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
        // Accesss to the native XHR object is required to add event listeners
        // for the upload progress event:
        if (xhr.upload) {
            $(xhr.upload).bind('progress', function (e) {
                var oe = e.originalEvent;
                // Make sure the progress event properties get copied over:
                e.lengthComputable = oe.lengthComputable;
                e.loaded = oe.loaded;
                e.total = oe.total;
                that._onProgress(e, options);
            });
            options.xhr = function () {
                return xhr;
            };
        }
    };

    nUIFileUpload.prototype._isInstanceOf = function (type, obj) {
        // Cross-frame instanceof check
        return Object.prototype.toString.call(obj) === '[object ' + type + ']';
    };

    nUIFileUpload.prototype._initXHRData = function (options) {
        var that = this,
            formData,
            file = options.files[0],
            // Ignore non-multipart setting if not supported:
            multipart = options.multipart || !$.support.xhrFileUpload,
            paramName = $.type(options.paramName) === 'array' ?
                options.paramName[0] : options.paramName;
        options.headers = $.extend({}, options.headers);
        if (options.contentRange) {
            options.headers['Content-Range'] = options.contentRange;
        }
        if (!multipart || options.blob || !this._isInstanceOf('File', file)) {
            options.headers['Content-Disposition'] = 'attachment; filename="' +
                encodeURI(file.name) + '"';
        }
        if (!multipart) {
            options.contentType = file.type || 'application/octet-stream';
            options.data = options.blob || file;
        } else if ($.support.xhrFormDataFileUpload) {
            if (options.postMessage) {
                // window.postMessage does not allow sending FormData
                // objects, so we just add the File/Blob objects to
                // the formData array and let the postMessage window
                // create the FormData object out of this array:
                formData = this._getFormData(options);
                if (options.blob) {
                    formData.push({
                        name: paramName,
                        value: options.blob
                    });
                } else {
                    $.each(options.files, function (index, file) {
                        formData.push({
                            name: ($.type(options.paramName) === 'array' &&
                                options.paramName[index]) || paramName,
                            value: file
                        });
                    });
                }
            } else {
                if (that._isInstanceOf('FormData', options.formData)) {
                    formData = options.formData;
                } else {
                    formData = new FormData();
                    $.each(this._getFormData(options), function (index, field) {
                        formData.append(field.name, field.value);
                    });
                }
                if (options.blob) {
                    formData.append(paramName, options.blob, file.name);
                } else {
                    $.each(options.files, function (index, file) {
                        // This check allows the tests to run with
                        // dummy objects:
                        if (that._isInstanceOf('File', file) ||
                            that._isInstanceOf('Blob', file)) {
                            formData.append(
                                ($.type(options.paramName) === 'array' &&
                                    options.paramName[index]) || paramName,
                                file,
                                file.uploadName || file.name
                            );
                        }
                    });
                }
            }
            options.data = formData;
        }
        // Blob reference is not needed anymore, free memory:
        options.blob = null;
    };

    nUIFileUpload.prototype._initIframeSettings = function (options) {
        var targetHost = $('<a></a>').prop('href', options.url).prop('host');
        // Setting the dataType to iframe enables the iframe transport:
        options.dataType = 'iframe ' + (options.dataType || '');
        // The iframe transport accepts a serialized array as form data:
        options.formData = this._getFormData(options);
        // Add redirect url to form data on cross-domain uploads:
        if (options.redirect && targetHost && targetHost !== location.host) {
            options.formData.push({
                name: options.redirectParamName || 'redirect',
                value: options.redirect
            });
        }
    };

    nUIFileUpload.prototype._initDataSettings = function (options) {
        if (this._isXHRUpload(options)) {
            if (!this._chunkedUpload(options, true)) {
                if (!options.data) {
                    this._initXHRData(options);
                }
                this._initProgressListener(options);
            }
            if (options.postMessage) {
                // Setting the dataType to postmessage enables the
                // postMessage transport:
                options.dataType = 'postmessage ' + (options.dataType || '');
            }
        } else {
            this._initIframeSettings(options);
        }
    };

    nUIFileUpload.prototype._getParamName = function (options) {
        var fileInput = $(options.fileInput),
            paramName = options.paramName;
        if (!paramName) {
            paramName = [];
            fileInput.each(function () {
                var input = $(this),
                    name = input.prop('name') || 'files[]',
                    i = (input.prop('files') || [1]).length;
                while (i) {
                    paramName.push(name);
                    i -= 1;
                }
            });
            if (!paramName.length) {
                paramName = [fileInput.prop('name') || 'files[]'];
            }
        } else if (!$.isArray(paramName)) {
            paramName = [paramName];
        }
        return paramName;
    };

    nUIFileUpload.prototype._initFormSettings = function (options) {
        // Retrieve missing options from the input field and the
        // associated form, if available:
        if (!options.form || !options.form.length) {
            options.form = $(options.fileInput.prop('form'));
            // If the given file input doesn't have an associated form,
            // use the default widget file input's form:
            if (!options.form.length) {
                options.form = $(this.options.fileInput.prop('form'));
            }
        }
        options.paramName = this._getParamName(options);
        if (!options.url) {
            options.url = options.form.prop('action') || location.href;
        }
        // The HTTP request method must be "POST" or "PUT":
        options.type = (options.type ||
            ($.type(options.form.prop('method')) === 'string' &&
                options.form.prop('method')) || ''
        ).toUpperCase();
        if (options.type !== 'POST' && options.type !== 'PUT' &&
            options.type !== 'PATCH') {
            options.type = 'POST';
        }
        if (!options.formAcceptCharset) {
            options.formAcceptCharset = options.form.attr('accept-charset');
        }
    };

    nUIFileUpload.prototype._getAJAXSettings = function (data) {
        var options = $.extend({}, this.options, data);
        this._initFormSettings(options);
        this._initDataSettings(options);
        return options;
    };

    // jQuery 1.6 doesn't provide .state(),
    // while jQuery 1.8+ removed .isRejected() and .isResolved():
    nUIFileUpload.prototype._getDeferredState = function (deferred) {
        if (deferred.state) {
            return deferred.state();
        }
        if (deferred.isResolved()) {
            return 'resolved';
        }
        if (deferred.isRejected()) {
            return 'rejected';
        }
        return 'pending';
    };

    // Maps jqXHR callbacks to the equivalent
    // methods of the given Promise object:
    nUIFileUpload.prototype._enhancePromise = function (promise) {
        promise.success = promise.done;
        promise.error = promise.fail;
        promise.complete = promise.always;
        return promise;
    };

    // Creates and returns a Promise object enhanced with
    // the jqXHR methods abort, success, error and complete:
    nUIFileUpload.prototype._getXHRPromise = function (resolveOrReject, context, args) {
        var dfd = $.Deferred(),
            promise = dfd.promise();
        context = context || this.options.context || promise;
        if (resolveOrReject === true) {
            dfd.resolveWith(context, args);
        } else if (resolveOrReject === false) {
            dfd.rejectWith(context, args);
        }
        promise.abort = dfd.promise;
        return this._enhancePromise(promise);
    };

    // Adds convenience methods to the data callback argument:
    nUIFileUpload.prototype._addConvenienceMethods = function (e, data) {
        var that = this,
            getPromise = function (args) {
                return $.Deferred().resolveWith(that, args).promise();
            };
        data.process = function (resolveFunc, rejectFunc) {
            if (resolveFunc || rejectFunc) {
                data._processQueue = this._processQueue =
                    (this._processQueue || getPromise([this])).pipe(
                        function () {
                            if (data.errorThrown) {
                                return $.Deferred()
                                    .rejectWith(that, [data]).promise();
                            }
                            return getPromise(arguments);
                        }
                    ).pipe(resolveFunc, rejectFunc);
            }
            return this._processQueue || getPromise([this]);
        };
        data.submit = function () {
            if (this.state() !== 'pending') {
                data.jqXHR = this.jqXHR =
                    (that._trigger(
                        'submit',
                        $.Event('submit', {delegatedEvent: e}),
                        this
                    ) !== false) && that._onSend(e, this);
            }
            return this.jqXHR || that._getXHRPromise();
        };
        data.abort = function () {
            if (this.jqXHR) {
                return this.jqXHR.abort();
            }
            this.errorThrown = 'abort';
            that._trigger('fail', null, this);
            return that._getXHRPromise(false);
        };
        data.state = function () {
            if (this.jqXHR) {
                return that._getDeferredState(this.jqXHR);
            }
            if (this._processQueue) {
                return that._getDeferredState(this._processQueue);
            }
        };
        data.processing = function () {
            return !this.jqXHR && this._processQueue && that
                ._getDeferredState(this._processQueue) === 'pending';
        };
        data.progress = function () {
            return this._progress;
        };
        data.response = function () {
            return this._response;
        };
    };

    // Parses the Range header from the server response
    // and returns the uploaded bytes:
    nUIFileUpload.prototype._getUploadedBytes = function (jqXHR) {
        var range = jqXHR.getResponseHeader('Range'),
            parts = range && range.split('-'),
            upperBytesPos = parts && parts.length > 1 &&
                parseInt(parts[1], 10);
        return upperBytesPos && upperBytesPos + 1;
    };

    // Uploads a file in multiple, sequential requests
    // by splitting the file up in multiple blob chunks.
    // If the second parameter is true, only tests if the file
    // should be uploaded in chunks, but does not invoke any
    // upload requests:
    nUIFileUpload.prototype._chunkedUpload = function (options, testOnly) {
        options.uploadedBytes = options.uploadedBytes || 0;
        var that = this,
            file = options.files[0],
            fs = file.size,
            ub = options.uploadedBytes,
            mcs = options.maxChunkSize || fs,
            slice = this._blobSlice,
            dfd = $.Deferred(),
            promise = dfd.promise(),
            jqXHR,
            upload;
        if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
            options.data) {
            return false;
        }
        if (testOnly) {
            return true;
        }
        if (ub >= fs) {
            file.error = options.i18n('uploadedBytes');
            return this._getXHRPromise(
                false,
                options.context,
                [null, 'error', file.error]
            );
        }
        // The chunk upload method:
        upload = function () {
            // Clone the options object for each chunk upload:
            var o = $.extend({}, options),
                currentLoaded = o._progress.loaded;
            o.blob = slice.call(
                file,
                ub,
                ub + mcs,
                file.type
            );
            // Store the current chunk size, as the blob itself
            // will be dereferenced after data processing:
            o.chunkSize = o.blob.size;
            // Expose the chunk bytes position range:
            o.contentRange = 'bytes ' + ub + '-' +
                (ub + o.chunkSize - 1) + '/' + fs;
            // Process the upload data (the blob and potential form data):
            that._initXHRData(o);
            // Add progress listeners for this chunk upload:
            that._initProgressListener(o);
            jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
                that._getXHRPromise(false, o.context))
                .done(function (result, textStatus, jqXHR) {
                    ub = that._getUploadedBytes(jqXHR) ||
                        (ub + o.chunkSize);
                    // Create a progress event if no final progress event
                    // with loaded equaling total has been triggered
                    // for this chunk:
                    if (currentLoaded + o.chunkSize - o._progress.loaded) {
                        that._onProgress($.Event('progress', {
                            lengthComputable: true,
                            loaded: ub - o.uploadedBytes,
                            total: ub - o.uploadedBytes
                        }), o);
                    }
                    options.uploadedBytes = o.uploadedBytes = ub;
                    o.result = result;
                    o.textStatus = textStatus;
                    o.jqXHR = jqXHR;
                    that._trigger('chunkdone', null, o);
                    that._trigger('chunkalways', null, o);
                    if (ub < fs) {
                        // File upload not yet complete,
                        // continue with the next chunk:
                        upload();
                    } else {
                        dfd.resolveWith(
                            o.context,
                            [result, textStatus, jqXHR]
                        );
                    }
                })
                .fail(function (jqXHR, textStatus, errorThrown) {
                    o.jqXHR = jqXHR;
                    o.textStatus = textStatus;
                    o.errorThrown = errorThrown;
                    that._trigger('chunkfail', null, o);
                    that._trigger('chunkalways', null, o);
                    dfd.rejectWith(
                        o.context,
                        [jqXHR, textStatus, errorThrown]
                    );
                });
        };
        this._enhancePromise(promise);
        promise.abort = function () {
            return jqXHR.abort();
        };
        upload();
        return promise;
    };

    nUIFileUpload.prototype._beforeSend = function (e, data) {
        if (this._active === 0) {
            // the start callback is triggered when an upload starts
            // and no other uploads are currently running,
            // equivalent to the global ajaxStart event:
            this._trigger('start');
            // Set timer for global bitrate progress calculation:
            this._bitrateTimer = new this._BitrateTimer();
            // Reset the global progress values:
            this._progress.loaded = this._progress.total = 0;
            this._progress.bitrate = 0;
        }
        // Make sure the container objects for the .response() and
        // .progress() methods on the data object are available
        // and reset to their initial state:
        this._initResponseObject(data);
        this._initProgressObject(data);
        data._progress.loaded = data.loaded = data.uploadedBytes || 0;
        data._progress.total = data.total = this._getTotal(data.files) || 1;
        data._progress.bitrate = data.bitrate = 0;
        this._active += 1;
        // Initialize the global progress values:
        this._progress.loaded += data.loaded;
        this._progress.total += data.total;
    };

    nUIFileUpload.prototype._onDone = function (result, textStatus, jqXHR, options) {
        var total = options._progress.total,
            response = options._response;
        if (options._progress.loaded < total) {
            // Create a progress event if no final progress event
            // with loaded equaling total has been triggered:
            this._onProgress($.Event('progress', {
                lengthComputable: true,
                loaded: total,
                total: total
            }), options);
        }
        response.result = options.result = result;
        response.textStatus = options.textStatus = textStatus;
        response.jqXHR = options.jqXHR = jqXHR;
        this._trigger('done', null, options);
    };

    nUIFileUpload.prototype._onFail = function (jqXHR, textStatus, errorThrown, options) {
        var response = options._response;
        if (options.recalculateProgress) {
            // Remove the failed (error or abort) file upload from
            // the global progress calculation:
            this._progress.loaded -= options._progress.loaded;
            this._progress.total -= options._progress.total;
        }
        response.jqXHR = options.jqXHR = jqXHR;
        response.textStatus = options.textStatus = textStatus;
        response.errorThrown = options.errorThrown = errorThrown;
        this._trigger('fail', null, options);
    };

    nUIFileUpload.prototype._onAlways = function (jqXHRorResult, textStatus, jqXHRorError, options) {
        // jqXHRorResult, textStatus and jqXHRorError are added to the
        // options object via done and fail callbacks
        this._trigger('always', null, options);
    };

    nUIFileUpload.prototype._onSend = function (e, data) {
        if (!data.submit) {
            this._addConvenienceMethods(e, data);
        }
        var that = this,
            jqXHR,
            aborted,
            slot,
            pipe,
            options = that._getAJAXSettings(data),
            send = function () {
                that._sending += 1;
                // Set timer for bitrate progress calculation:
                options._bitrateTimer = new that._BitrateTimer();
                jqXHR = jqXHR || (
                    ((aborted || that._trigger(
                        'send',
                        $.Event('send', {delegatedEvent: e}),
                        options
                        ) === false) &&
                        that._getXHRPromise(false, options.context, aborted)) ||
                    that._chunkedUpload(options) || $.ajax(options)
                ).done(function (result, textStatus, jqXHR) {
                    that._onDone(result, textStatus, jqXHR, options);
                }).fail(function (jqXHR, textStatus, errorThrown) {
                    that._onFail(jqXHR, textStatus, errorThrown, options);
                }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
                    that._onAlways(
                        jqXHRorResult,
                        textStatus,
                        jqXHRorError,
                        options
                    );
                    that._sending -= 1;
                    that._active -= 1;
                    if (options.limitConcurrentUploads &&
                        options.limitConcurrentUploads > that._sending) {
                        // Start the next queued upload,
                        // that has not been aborted:
                        var nextSlot = that._slots.shift();
                        while (nextSlot) {
                            if (that._getDeferredState(nextSlot) === 'pending') {
                                nextSlot.resolve();
                                break;
                            }
                            nextSlot = that._slots.shift();
                        }
                    }
                    if (that._active === 0) {
                        // The stop callback is triggered when all uploads have
                        // been completed, equivalent to the global ajaxStop event:
                        that._trigger('stop');
                    }
                });
                return jqXHR;
            };
        this._beforeSend(e, options);
        if (this.options.sequentialUploads ||
            (this.options.limitConcurrentUploads &&
                this.options.limitConcurrentUploads <= this._sending)) {
            if (this.options.limitConcurrentUploads > 1) {
                slot = $.Deferred();
                this._slots.push(slot);
                pipe = slot.pipe(send);
            } else {
                this._sequence = this._sequence.pipe(send, send);
                pipe = this._sequence;
            }
            // Return the piped Promise object, enhanced with an abort method,
            // which is delegated to the jqXHR object of the current upload,
            // and jqXHR callbacks mapped to the equivalent Promise methods:
            pipe.abort = function () {
                aborted = [undefined, 'abort', 'abort'];
                if (!jqXHR) {
                    if (slot) {
                        slot.rejectWith(options.context, aborted);
                    }
                    return send();
                }
                return jqXHR.abort();
            };
            return this._enhancePromise(pipe);
        }
        return send();
    };

    nUIFileUpload.prototype._onAdd = function (e, data) {
        var that = this,
            result = true,
            options = $.extend({}, this.options, data),
            files = data.files,
            filesLength = files.length,
            limit = options.limitMultiFileUploads,
            limitSize = options.limitMultiFileUploadSize,
            overhead = options.limitMultiFileUploadSizeOverhead,
            batchSize = 0,
            paramName = this._getParamName(options),
            paramNameSet,
            paramNameSlice,
            fileSet,
            i,
            j = 0;
        if (limitSize && (!filesLength || files[0].size === undefined)) {
            limitSize = undefined;
        }
        if (!(options.singleFileUploads || limit || limitSize) || !this._isXHRUpload(options)) {
            fileSet = [files];
            paramNameSet = [paramName];
        } else if (!(options.singleFileUploads || limitSize) && limit) {
            fileSet = [];
            paramNameSet = [];
            for (i = 0; i < filesLength; i += limit) {
                fileSet.push(files.slice(i, i + limit));
                paramNameSlice = paramName.slice(i, i + limit);
                if (!paramNameSlice.length) {
                    paramNameSlice = paramName;
                }
                paramNameSet.push(paramNameSlice);
            }
        } else if (!options.singleFileUploads && limitSize) {
            fileSet = [];
            paramNameSet = [];
            for (i = 0; i < filesLength; i = i + 1) {
                batchSize += files[i].size + overhead;
                if (i + 1 === filesLength ||
                    ((batchSize + files[i + 1].size + overhead) > limitSize) ||
                    (limit && i + 1 - j >= limit)) {
                    fileSet.push(files.slice(j, i + 1));
                    paramNameSlice = paramName.slice(j, i + 1);
                    if (!paramNameSlice.length) {
                        paramNameSlice = paramName;
                    }
                    paramNameSet.push(paramNameSlice);
                    j = i + 1;
                    batchSize = 0;
                }
            }
        } else {
            paramNameSet = paramName;
        }
        data.originalFiles = files;
        $.each(fileSet || files, function (index, element) {
            var newData = $.extend({}, data);
            newData.files = fileSet ? element : [element];
            newData.paramName = paramNameSet[index];
            that._initResponseObject(newData);
            that._initProgressObject(newData);
            that._addConvenienceMethods(e, newData);
            result = that._trigger(
                'add',
                $.Event('add', {delegatedEvent: e}),
                newData
            );
            return result;
        });
        return result;
    };

    nUIFileUpload.prototype._replaceFileInput = function (data) {
        var input = data.fileInput,
            inputClone = input.clone(true);
        // Add a reference for the new cloned file input to the data argument:
        data.fileInputClone = inputClone;
        $('<form></form>').append(inputClone)[0].reset();
        // Detaching allows to insert the fileInput on another form
        // without loosing the file input value:
        input.after(inputClone).detach();
        // Avoid memory leaks with the detached file input:
        $.cleanData(input.unbind('remove'));
        // Replace the original file input element in the fileInput
        // elements set with the clone, which has been copied including
        // event handlers:
        this.options.fileInput = this.options.fileInput.map(function (i, el) {
            if (el === input[0]) {
                return inputClone[0];
            }
            return el;
        });
        // If the widget has been initialized on the file input itself,
        // override this.element with the file input clone:
        if (input[0] === this.element[0]) {
            this.element = inputClone;
        }
    };

    nUIFileUpload.prototype._handleFileTreeEntry = function (entry, path) {
        var that = this,
            dfd = $.Deferred(),
            errorHandler = function (e) {
                if (e && !e.entry) {
                    e.entry = entry;
                }
                // Since $.when returns immediately if one
                // Deferred is rejected, we use resolve instead.
                // This allows valid files and invalid items
                // to be returned together in one set:
                dfd.resolve([e]);
            },
            successHandler = function (entries) {
                that._handleFileTreeEntries(
                    entries,
                    path + entry.name + '/'
                ).done(function (files) {
                    dfd.resolve(files);
                }).fail(errorHandler);
            },
            readEntries = function () {
                dirReader.readEntries(function (results) {
                    if (!results.length) {
                        successHandler(entries);
                    } else {
                        entries = entries.concat(results);
                        readEntries();
                    }
                }, errorHandler);
            },
            dirReader, entries = [];
        path = path || '';
        if (entry.isFile) {
            if (entry._file) {
                // Workaround for Chrome bug #149735
                entry._file.relativePath = path;
                dfd.resolve(entry._file);
            } else {
                entry.file(function (file) {
                    file.relativePath = path;
                    dfd.resolve(file);
                }, errorHandler);
            }
        } else if (entry.isDirectory) {
            dirReader = entry.createReader();
            readEntries();
        } else {
            // Return an empy list for file system items
            // other than files or directories:
            dfd.resolve([]);
        }
        return dfd.promise();
    };

    nUIFileUpload.prototype._handleFileTreeEntries = function (entries, path) {
        var that = this;
        return $.when.apply(
            $,
            $.map(entries, function (entry) {
                return that._handleFileTreeEntry(entry, path);
            })
        ).pipe(function () {
            return Array.prototype.concat.apply(
                [],
                arguments
            );
        });
    };

    nUIFileUpload.prototype._getDroppedFiles = function (dataTransfer) {
        dataTransfer = dataTransfer || {};
        var items = dataTransfer.items;
        if (items && items.length && (items[0].webkitGetAsEntry ||
            items[0].getAsEntry)) {
            return this._handleFileTreeEntries(
                $.map(items, function (item) {
                    var entry;
                    if (item.webkitGetAsEntry) {
                        entry = item.webkitGetAsEntry();
                        if (entry) {
                            // Workaround for Chrome bug #149735:
                            entry._file = item.getAsFile();
                        }
                        return entry;
                    }
                    return item.getAsEntry();
                })
            );
        }
        return $.Deferred().resolve(
            $.makeArray(dataTransfer.files)
        ).promise();
    };

    nUIFileUpload.prototype._getSingleFileInputFiles = function (fileInput) {
        fileInput = $(fileInput);
        var entries = fileInput.prop('webkitEntries') ||
            fileInput.prop('entries'),
            files,
            value;
        if (entries && entries.length) {
            return this._handleFileTreeEntries(entries);
        }
        files = $.makeArray(fileInput.prop('files'));
        if (!files.length) {
            value = fileInput.prop('value');
            if (!value) {
                return $.Deferred().resolve([]).promise();
            }
            // If the files property is not available, the browser does not
            // support the File API and we add a pseudo File object with
            // the input value as name with path information removed:
            files = [{name: value.replace(/^.*\\/, '')}];
        } else if (files[0].name === undefined && files[0].fileName) {
            // File normalization for Safari 4 and Firefox 3:
            $.each(files, function (index, file) {
                file.name = file.fileName;
                file.size = file.fileSize;
            });
        }
        return $.Deferred().resolve(files).promise();
    };

    nUIFileUpload.prototype._getFileInputFiles = function (fileInput) {
        if (!(fileInput instanceof $) || fileInput.length === 1) {
            return this._getSingleFileInputFiles(fileInput);
        }
        return $.when.apply(
            $,
            $.map(fileInput, this._getSingleFileInputFiles)
        ).pipe(function () {
            return Array.prototype.concat.apply(
                [],
                arguments
            );
        });
    };

    nUIFileUpload.prototype._onChange = function (e) {
        var that = this,
            data = {
                fileInput: $(e.target),
                form: $(e.target.form)
            };
        this._getFileInputFiles(data.fileInput).always(function (files) {
            data.files = files;
            if (that.options.replaceFileInput) {
                that._replaceFileInput(data);
            }
            if (that._trigger(
                'change',
                $.Event('change', {delegatedEvent: e}),
                data
            ) !== false) {
                that._onAdd(e, data);
            }
        });
    };

    nUIFileUpload.prototype._onPaste = function (e) {
        var items = e.originalEvent && e.originalEvent.clipboardData &&
            e.originalEvent.clipboardData.items,
            data = {files: []};
        if (items && items.length) {
            $.each(items, function (index, item) {
                var file = item.getAsFile && item.getAsFile();
                if (file) {
                    data.files.push(file);
                }
            });
            if (this._trigger(
                'paste',
                $.Event('paste', {delegatedEvent: e}),
                data
            ) !== false) {
                this._onAdd(e, data);
            }
        }
    }
    ;

    nUIFileUpload.prototype._onDrop = function (e) {
        e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
        var that = this,
            dataTransfer = e.dataTransfer,
            data = {};
        if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
            e.preventDefault();
            e.stopPropagation();
            this._getDroppedFiles(dataTransfer).always(function (files) {
                data.files = files;
                if (that._trigger(
                    'drop',
                    $.Event('drop', {delegatedEvent: e}),
                    data
                ) !== false) {
                    that._onAdd(e, data);
                }
            });
        }
    };

    nUIFileUpload.prototype._onDragOver = getDragHandler('dragover');

    nUIFileUpload.prototype._onDragEnter = getDragHandler('dragenter');

    nUIFileUpload.prototype._onDragLeave = getDragHandler('dragleave');

    nUIFileUpload.prototype._initEventHandlers = function () {
        if (this._isXHRUpload(this.options)) {
            $(this.options.dropZone).on({
                dragover: $.proxy(this._onDragOver, this),
                drop: $.proxy(this._onDrop, this),
                // event.preventDefault() on dragenter is required for IE10+:
                dragenter: $.proxy(this._onDragEnter, this),
                // dragleave is not required, but added for completeness:
                dragleave: $.proxy(this._onDragLeave, this)
            });
            $(this.options.pasteZone).on({
                paste: $.proxy(this._onPaste, this)
            });
        }
        if ($.support.fileInput) {
            $(this.options.fileInput).on({
                change: $.proxy(this._onChange, this)
            });
        }
    };

    nUIFileUpload.prototype._destroyEventHandlers = function () {
        $(this.options.dropZone).off('dragenter dragleave dragover drop');
        $(this.options.pasteZone).off('paste');
        $(this.options.fileInput).off('change');
    };

    nUIFileUpload.prototype.setOption = function (key, value) {
        var reinit = $.inArray(key, this._specialOptions) !== -1;
        if (reinit) {
            this._destroyEventHandlers();
        }

        N2Classes.nUIWidgetBase.prototype.setOption.apply(this, arguments);

        if (reinit) {
            this._initSpecialOptions();
            this._initEventHandlers();
        }
    };

    nUIFileUpload.prototype._initSpecialOptions = function () {
        var options = this.options;
        if (options.fileInput === undefined) {
            options.fileInput = this.element.is('input[type="file"]') ?
                this.element : this.element.find('input[type="file"]');
        } else if (!(options.fileInput instanceof $)) {
            options.fileInput = $(options.fileInput);
        }
        if (!(options.dropZone instanceof $)) {
            options.dropZone = $(options.dropZone);
        }
        if (!(options.pasteZone instanceof $)) {
            options.pasteZone = $(options.pasteZone);
        }
    };

    nUIFileUpload.prototype._getRegExp = function (str) {
        var parts = str.split('/'),
            modifiers = parts.pop();
        parts.shift();
        return new RegExp(parts.join('/'), modifiers);
    };

    nUIFileUpload.prototype._isRegExpOption = function (key, value) {
        return key !== 'url' && $.type(value) === 'string' && /^\/.*\/[igm]{0,3}$/.test(value);
    };

    nUIFileUpload.prototype._initDataAttributes = function () {
        var that = this,
            options = this.options,
            data = this.element.data();
        // Initialize options set via HTML5 data-attributes:
        $.each(
            this.element[0].attributes,
            function (index, attr) {
                var key = attr.name.toLowerCase(),
                    value;
                if (/^data-/.test(key)) {
                    // Convert hyphen-ated key to camelCase:
                    key = key.slice(5).replace(/-[a-z]/g, function (str) {
                        return str.charAt(1).toUpperCase();
                    });
                    value = data[key];
                    if (that._isRegExpOption(key, value)) {
                        value = that._getRegExp(value);
                    }
                    options[key] = value;
                }
            }
        );
    };

    nUIFileUpload.prototype.create = function () {
        this._initDataAttributes();
        this._initSpecialOptions();
        this._slots = [];
        this._sequence = this._getXHRPromise(true);
        this._sending = this._active = 0;
        this._initProgressObject(this);
        this._initEventHandlers();
    };

    // This method is exposed to the widget API and allows to query
    // the number of active uploads:
    nUIFileUpload.prototype.active = function () {
        return this._active;
    };

    // This method is exposed to the widget API and allows to query
    // the widget upload progress.
    // It returns an object with loaded, total and bitrate properties
    // for the running uploads:
    nUIFileUpload.prototype.progress = function () {
        return this._progress;
    };

    // This method is exposed to the widget API and allows adding files
    // using the fileupload API. The data parameter accepts an object which
    // must have a files property and can contain additional options:
    // .nUIFileUpload('add', {files: filesList});
    nUIFileUpload.prototype.add = function (data) {
        var that = this;
        if (!data || this.options.disabled) {
            return;
        }
        if (data.fileInput && !data.files) {
            this._getFileInputFiles(data.fileInput).always(function (files) {
                data.files = files;
                that._onAdd(null, data);
            });
        } else {
            data.files = $.makeArray(data.files);
            this._onAdd(null, data);
        }
    };

    // This method is exposed to the widget API and allows sending files
    // using the nUIFileUpload API. The data parameter accepts an object which
    // must have a files or fileInput property and can contain additional options:
    // .nUIFileUpload('send', {files: filesList});
    // The method returns a Promise object for the file upload call.
    nUIFileUpload.prototype.send = function (data) {
        if (data && !this.options.disabled) {
            if (data.fileInput && !data.files) {
                var that = this,
                    dfd = $.Deferred(),
                    promise = dfd.promise(),
                    jqXHR,
                    aborted;
                promise.abort = function () {
                    aborted = true;
                    if (jqXHR) {
                        return jqXHR.abort();
                    }
                    dfd.reject(null, 'abort', 'abort');
                    return promise;
                };
                this._getFileInputFiles(data.fileInput).always(
                    function (files) {
                        if (aborted) {
                            return;
                        }
                        if (!files.length) {
                            dfd.reject();
                            return;
                        }
                        data.files = files;
                        jqXHR = that._onSend(null, data);
                        jqXHR.then(
                            function (result, textStatus, jqXHR) {
                                dfd.resolve(result, textStatus, jqXHR);
                            },
                            function (jqXHR, textStatus, errorThrown) {
                                dfd.reject(jqXHR, textStatus, errorThrown);
                            }
                        );
                    }
                );
                return this._enhancePromise(promise);
            }
            data.files = $.makeArray(data.files);
            if (data.files.length) {
                return this._onSend(null, data);
            }
        }
        return this._getXHRPromise(false, data && data.context);
    };

    N2Classes.nUIWidgetBase.register('nUIFileUpload');

    return nUIFileUpload;
});
N2D('HorizontalScrollBar', function ($) {
    "use strict";

    /**
     * @memberOf N2Classes
     *
     * @param $container
     * @constructor
     */
    function HorizontalScrollBar($container) {
        this.$container = $container;
        this.$document = $(document);

        this.currentLeft = 0;

        this.$viewport = $container.find('.n2-scroll-viewport');
        this.$content = $container.find('.n2-scroll-content');

        this.$track = $container.find('.n2-scroll-track');
        this.$grip = $container.find('.n2-scroll-grip');

        this.side = window.n2const.isRTL() ? "right" : "left";
        this.modifier = window.n2const.isRTL() ? -1 : 1;

        this.$grip.on('mousedown.scrollbar', $.proxy(this.mouseDown, this));

        this.update();

        $(window).resize($.proxy(this.update, this));
    }

    HorizontalScrollBar.prototype.update = function () {
        this.viewportWidth = this.$viewport.width();
        this.contentWidth = this.$content.outerWidth();
        this.trackWidth = this.$track.width();

        this.ratio = Math.min(1, this.viewportWidth / this.contentWidth);

        this.gripWidth = Math.max(20, Math.floor(this.ratio * this.trackWidth));

        this.$grip.width(this.gripWidth);

        this.setLeft(this.currentLeft);


        this.$container.toggleClass("n2-scroll-disable", this.ratio === 1);
    };

    HorizontalScrollBar.prototype.setLeft = function (left) {
        left = Math.max(0, Math.min(this.trackWidth - this.gripWidth, left));

        this.$grip.css(this.side, left);
        this.$content.css(this.side, -1 * Math.ceil(left / this.ratio));

        this.currentLeft = left;
    };

    HorizontalScrollBar.prototype.mouseDown = function (e) {
        this.context = {
            pageX: e.pageX,
            left: this.currentLeft
        };
        this.$document.on({
            'mousemove.scrollbar': $.proxy(this.mouseMove, this),
            'mouseup.scrollbar': $.proxy(this.mouseUp, this)
        });
    };

    HorizontalScrollBar.prototype.mouseMove = function (e) {

        this.setLeft(this.context.left + (e.pageX - this.context.pageX) * this.modifier);
    };

    HorizontalScrollBar.prototype.mouseUp = function (e) {

        this.mouseMove(e);

        /**
         * Cleanup when the interaction ends.
         */
        this.$document.off('.scrollbar');
        delete this.context;
    };

    return HorizontalScrollBar;
});
/*
 * jQuery Iframe Transport Plugin 1.8.3
 * https://github.com/blueimp/jQuery-File-Upload
 *
 * Copyright 2011, Sebastian Tschan
 * https://blueimp.net
 *
 * Licensed under the MIT license:
 * http://www.opensource.org/licenses/MIT
 */

/* global define, require, window, document */

N2D('ajaxTransportIframe', function ($, undefined) {
    'use strict';

    // Helper variable to create unique names for the transport iframes:
    var counter = 0;

    // The iframe transport accepts four additional options:
    // options.fileInput: a jQuery collection of file input fields
    // options.paramName: the parameter name for the file form data,
    //  overrides the name property of the file input field(s),
    //  can be a string or an array of strings.
    // options.formData: an array of objects with name and value properties,
    //  equivalent to the return data of .serializeArray(), e.g.:
    //  [{name: 'a', value: 1}, {name: 'b', value: 2}]
    // options.initialIframeSrc: the URL of the initial iframe src,
    //  by default set to "javascript:false;"
    $.ajaxTransport('iframe', function (options) {
        if (options.async) {
            // javascript:false as initial iframe src
            // prevents warning popups on HTTPS in IE6:
            /*jshint scripturl: true */
            var initialIframeSrc = options.initialIframeSrc || 'javascript:false;',
                /*jshint scripturl: false */
                form,
                iframe,
                addParamChar;
            return {
                send: function (_, completeCallback) {
                    form = $('<form style="display:none;"></form>');
                    form.attr('accept-charset', options.formAcceptCharset);
                    addParamChar = /\?/.test(options.url) ? '&' : '?';
                    // XDomainRequest only supports GET and POST:
                    if (options.type === 'DELETE') {
                        options.url = options.url + addParamChar + '_method=DELETE';
                        options.type = 'POST';
                    } else if (options.type === 'PUT') {
                        options.url = options.url + addParamChar + '_method=PUT';
                        options.type = 'POST';
                    } else if (options.type === 'PATCH') {
                        options.url = options.url + addParamChar + '_method=PATCH';
                        options.type = 'POST';
                    }
                    // IE versions below IE8 cannot set the name property of
                    // elements that have already been added to the DOM,
                    // so we set the name along with the iframe HTML markup:
                    counter += 1;
                    iframe = $(
                        '<iframe src="' + initialIframeSrc +
                        '" name="iframe-transport-' + counter + '"></iframe>'
                    ).bind('load', function () {
                        var fileInputClones,
                            paramNames = $.isArray(options.paramName) ?
                                options.paramName : [options.paramName];
                        iframe
                            .unbind('load')
                            .bind('load', function () {
                                var response;
                                // Wrap in a try/catch block to catch exceptions thrown
                                // when trying to access cross-domain iframe contents:
                                try {
                                    response = iframe.contents();
                                    // Google Chrome and Firefox do not throw an
                                    // exception when calling iframe.contents() on
                                    // cross-domain requests, so we unify the response:
                                    if (!response.length || !response[0].firstChild) {
                                        throw new Error();
                                    }
                                } catch (e) {
                                    response = undefined;
                                }
                                // The complete callback returns the
                                // iframe content document as response object:
                                completeCallback(
                                    200,
                                    'success',
                                    {'iframe': response}
                                );
                                // Fix for IE endless progress bar activity bug
                                // (happens on form submits to iframe targets):
                                $('<iframe src="' + initialIframeSrc + '"></iframe>')
                                    .appendTo(form);
                                window.setTimeout(function () {
                                    // Removing the form in a setTimeout call
                                    // allows Chrome's developer tools to display
                                    // the response result
                                    form.remove();
                                }, 0);
                            });
                        form
                            .prop('target', iframe.prop('name'))
                            .prop('action', options.url)
                            .prop('method', options.type);
                        if (options.formData) {
                            $.each(options.formData, function (index, field) {
                                $('<input type="hidden"/>')
                                    .prop('name', field.name)
                                    .val(field.value)
                                    .appendTo(form);
                            });
                        }
                        if (options.fileInput && options.fileInput.length &&
                            options.type === 'POST') {
                            fileInputClones = options.fileInput.clone();
                            // Insert a clone for each file input field:
                            options.fileInput.after(function (index) {
                                return fileInputClones[index];
                            });
                            if (options.paramName) {
                                options.fileInput.each(function (index) {
                                    $(this).prop(
                                        'name',
                                        paramNames[index] || options.paramName
                                    );
                                });
                            }
                            // Appending the file input fields to the hidden form
                            // removes them from their original location:
                            form
                                .append(options.fileInput)
                                .prop('enctype', 'multipart/form-data')
                                // enctype must be set as encoding for IE:
                                .prop('encoding', 'multipart/form-data');
                            // Remove the HTML5 form attribute from the input(s):
                            options.fileInput.removeAttr('form');
                        }
                        form.submit();
                        // Insert the file input fields at their original location
                        // by replacing the clones with the originals:
                        if (fileInputClones && fileInputClones.length) {
                            options.fileInput.each(function (index, input) {
                                var clone = $(fileInputClones[index]);
                                // Restore the original name and form properties:
                                $(input)
                                    .prop('name', clone.prop('name'))
                                    .attr('form', clone.attr('form'));
                                clone.replaceWith(input);
                            });
                        }
                    });
                    form.append(iframe).appendTo(document.body);
                },
                abort: function () {
                    if (iframe) {
                        // javascript:false as iframe src aborts the request
                        // and prevents warning popups on HTTPS in IE6.
                        // concat is used to avoid the "Script URL" JSLint error:
                        iframe
                            .unbind('load')
                            .prop('src', initialIframeSrc);
                    }
                    if (form) {
                        form.remove();
                    }
                }
            };
        }
    });

    // The iframe transport returns the iframe content document as response.
    // The following adds converters from iframe to text, json, html, xml
    // and script.
    // Please note that the Content-Type for JSON responses has to be text/plain
    // or text/html, if the browser doesn't include application/json in the
    // Accept header, else IE will show a download dialog.
    // The Content-Type for XML responses on the other hand has to be always
    // application/xml or text/xml, so IE properly parses the XML response.
    // See also
    // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation
    $.ajaxSetup({
        converters: {
            'iframe text': function (iframe) {
                return iframe && $(iframe[0].body).text();
            },
            'iframe json': function (iframe) {
                return iframe && $.parseJSON($(iframe[0].body).text());
            },
            'iframe html': function (iframe) {
                return iframe && $(iframe[0].body).html();
            },
            'iframe xml': function (iframe) {
                var xmlDoc = iframe && iframe[0];
                return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc :
                    $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) ||
                        $(xmlDoc.body).html());
            },
            'iframe script': function (iframe) {
                return iframe && $.globalEval($(iframe[0].body).text());
            }
        }
    });

});

N2D('nUIMouse', ['nUIWidgetBase'], function ($, undefined) {
    "use strict";

    var ie = !!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase());

    var mouseHandled = false;
    $(document).on("mouseup", function () {
        mouseHandled = false;
    });

    /**
     * @memberOf N2Classes
     *
     * @abstract
     * @constructor
     * @augments nUIWidgetBase
     */
    function nUIMouse(element, options) {
        this.widgetName = this.widgetName || 'nUIMouse';
        this.options = $.extend({}, {
            cancel: "input, textarea, button, select, option",
            distance: 1,
            delay: 0
        }, this.options);

        N2Classes.nUIWidgetBase.prototype.constructor.apply(this, arguments);
    }


    nUIMouse.prototype = Object.create(N2Classes.nUIWidgetBase.prototype);
    nUIMouse.prototype.constructor = nUIMouse;

    nUIMouse.prototype._mouseInit = function () {
        var that = this;
        this.element
            .on("mousedown." + this.widgetName, function (event) {
                return that._mouseDown(event);
            })
            .on("click." + this.widgetName, function (event) {
                if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) {
                    $.removeData(event.target, that.widgetName + ".preventClickEvent");
                    event.stopImmediatePropagation();
                    return false;
                }
            });

        this.started = false;
    };

    nUIMouse.prototype._mouseDestroy = function () {
        this.element.off("." + this.widgetName);
        if (this._mouseMoveDelegate) {
            this.document
                .off("mousemove." + this.widgetName, this._mouseMoveDelegate)
                .off("mouseup." + this.widgetName, this._mouseUpDelegate);
        }
    };

    nUIMouse.prototype._mouseDown = function (event) {
        // don't let more than one widget handle mouseStart
        if (mouseHandled) {
            return;
        }

        this._mouseMoved = false;

        // We may have missed mouseup (out of window)
        ( this._mouseStarted && this._mouseUp(event) );

        this._mouseDownEvent = event;

        var that = this,
            btnIsLeft = ( event.which === 1 ),

            // event.target.nodeName works around a bug in IE 8 with
            // disabled inputs (#7620)
            elIsCancel = ( typeof this.options.cancel === "string" && event.target.nodeName ?
                $(event.target).closest(this.options.cancel).length : false );
        if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
            return true;
        }

        this.mouseDelayMet = !this.options.delay;
        if (!this.mouseDelayMet) {
            this._mouseDelayTimer = setTimeout(function () {
                that.mouseDelayMet = true;
            }, this.options.delay);
        }

        if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
            this._mouseStarted = ( this._mouseStart(event) !== false );
            if (!this._mouseStarted) {
                event.preventDefault();
                return true;
            }
        }

        // Click event may never have fired (Gecko & Opera)
        if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) {
            $.removeData(event.target, this.widgetName + ".preventClickEvent");
        }

        // These delegates are required to keep context
        this._mouseMoveDelegate = function (event) {
            return that._mouseMove(event);
        };
        this._mouseUpDelegate = function (event) {
            return that._mouseUp(event);
        };

        this.document
            .on("mousemove." + this.widgetName, this._mouseMoveDelegate)
            .on("mouseup." + this.widgetName, this._mouseUpDelegate);

        event.preventDefault();

        mouseHandled = true;
        return true;
    };


    nUIMouse.prototype._mouseMove = function (event) {

        // Only check for mouseups outside the document if you've moved inside the document
        // at least once. This prevents the firing of mouseup in the case of IE<9, which will
        // fire a mousemove event if content is placed under the cursor. See #7778
        // Support: IE <9
        if (this._mouseMoved) {

            // IE mouseup check - mouseup happened when mouse was out of window
            if (ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) {
                return this._mouseUp(event);

                // Iframe mouseup check - mouseup occurred in another document
            } else if (!event.which) {

                // Support: Safari <=8 - 9
                // Safari sets which to 0 if you press any of the following keys
                // during a drag (#14461)
                if (event.originalEvent.altKey || event.originalEvent.ctrlKey ||
                    event.originalEvent.metaKey || event.originalEvent.shiftKey) {
                    this.ignoreMissingWhich = true;
                } else if (!this.ignoreMissingWhich) {
                    return this._mouseUp(event);
                }
            }
        }

        if (event.which || event.button) {
            this._mouseMoved = true;
        }

        if (this._mouseStarted) {
            this._mouseDrag(event);
            return event.preventDefault();
        }

        if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
            this._mouseStarted =
                ( this._mouseStart(this._mouseDownEvent, event) !== false );
            ( this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event) );
        }

        return !this._mouseStarted;
    };

    nUIMouse.prototype._mouseUp = function (event) {
        this.document
            .off("mousemove." + this.widgetName, this._mouseMoveDelegate)
            .off("mouseup." + this.widgetName, this._mouseUpDelegate);

        if (this._mouseStarted) {
            this._mouseStarted = false;

            if (event.target === this._mouseDownEvent.target) {
                $.data(event.target, this.widgetName + ".preventClickEvent", true);
            }

            this._mouseStop(event);
        }

        if (this._mouseDelayTimer) {
            clearTimeout(this._mouseDelayTimer);
            delete this._mouseDelayTimer;
        }

        this.ignoreMissingWhich = false;
        mouseHandled = false;
        event.preventDefault();
    };

    nUIMouse.prototype._mouseDistanceMet = function (event) {
        return ( Math.max(
                Math.abs(this._mouseDownEvent.pageX - event.pageX),
                Math.abs(this._mouseDownEvent.pageY - event.pageY)
            ) >= this.options.distance
        );
    };

    nUIMouse.prototype._mouseDelayMet = function (/* event */) {
        return this.mouseDelayMet;
    };

    // These are placeholder methods, to be overriden by extending plugin
    nUIMouse.prototype._mouseStart = function (/* event */) {
    };
    nUIMouse.prototype._mouseDrag = function (/* event */) {
    };
    nUIMouse.prototype._mouseStop = function (/* event */) {
    };
    nUIMouse.prototype._mouseCapture = function (/* event */) {
        return true;
    };

    return nUIMouse;
});
N2D('nUINormalSizing', ['nUIMouse'], function ($, undefined) {
    "use strict";

    /**
     * @memberOf N2Classes
     *
     * @constructor
     * @augments nUIMouse
     * @this nUINormalSizing
     */
    function nUINormalSizing(element, options) {
        this.element = $(element);

        this.widgetName = this.widgetName || 'nUINormalSizing';
        this.widgetEventPrefix = "normalsizing";
        this.multiplier = 1;

        this.options = $.extend({
            maxWidth: true,
            height: false,
            syncWidth: false,
            start: null,
            resizeMaxWidth: null,
            resizeHeight: null,
            stopMaxWidth: null,
            stopHeight: null
        }, this.options, options);

        N2Classes.nUIMouse.prototype.constructor.apply(this, arguments);

        this.create();
    }

    nUINormalSizing.prototype = Object.create(N2Classes.nUIMouse.prototype);
    nUINormalSizing.prototype.constructor = nUINormalSizing;


    nUINormalSizing.prototype.create = function () {

        this._setupHandles();

        this._mouseInit();
    };

    nUINormalSizing.prototype._destroy = function () {

        this._mouseDestroy();

        this.element
            .removeData(this.widgetName);

        for (var k in this.handles) {
            this.handles[k].remove();
        }

        return this;
    };

    nUINormalSizing.prototype._setupHandles = function () {
        var o = this.options, i, n = [], hname, axis;

        if (o.maxWidth) {
            n.push('w');
            n.push('e');
        }

        if (o.height) {
            n.push('s');
        }
        this.handles = {};

        for (i = 0; i < n.length; i++) {
            var handle = n[i];
            axis = $('<div class="nui-normal-sizing-handle nui-normal-sizing-' + handle + '">').css('zIndex', 90);
            this.handles[handle] = axis;
            this.element.append(axis);
        }

        if (o.maxWidth) {
            nextend.tooltip.addElement(this.handles.e, 'Max width');
            nextend.tooltip.addElement(this.handles.w, 'Max width');
        }

        if (o.height) {
            nextend.tooltip.addElement(this.handles.s, 'Height');
        }

        this._handles = this.element.find("> .nui-normal-sizing-handle");
        this._handles.addClass('n2-unselectable');
    };

    nUINormalSizing.prototype._removeHandles = function () {
        this._handles.remove();
    };

    nUINormalSizing.prototype._mouseCapture = function (event) {
        var handle,
            capture = false;
        for (handle in this.handles) {
            if (this.handles[handle][0] === event.target) {
                this.currentHandle = handle;
                return !this.options.disabled;
            }
        }

        return false;
    };

    nUINormalSizing.prototype._mouseStart = function (event) {
        var o = this.options,
            el = this.element;

        this.resizing = true;

        this.originalMousePosition = {left: event.pageX, top: event.pageY};

        switch (this.currentHandle) {
            case 'w':
            case 'e':
                this.originalValue = this.element.width();
                this.maxWidth = this.element.parent().width();

                this._trigger("start", event, 'maxwidth');

                if (this.element.css('align-self') == 'center') {
                    this.multiplier = 2;
                } else {
                    this.multiplier = 1;
                }
                break;
            case 's':
                this.originalValue = this.element.height();

                this._trigger("start", event, 'height');
                break;
        }

        this.element.addClass("nui-normal-sizing-resizing");


        $("body").addClass('n2-ss-normal-sizing-element');
        return true;
    };

    nUINormalSizing.prototype._parse_movement_s = function (e) {
        return e.pageY - this.originalMousePosition.top;
    };

    nUINormalSizing.prototype._parse_movement_e = function (e) {
        return (e.pageX - this.originalMousePosition.left) * this.multiplier;
    };

    nUINormalSizing.prototype._parse_movement_w = function (e) {
        return (this.originalMousePosition.left - e.pageX) * this.multiplier;
    };

    nUINormalSizing.prototype._mouseDrag = function (event) {
        var o = this.options;

        this.currentValue = nextend.roundHelper(this.originalValue + this['_parse_movement_' + this.currentHandle].call(this, event));

        switch (this.currentHandle) {
            case 'w':
            case 'e':
                if (this.currentValue <= this.maxWidth) {
                    this.element.css('maxWidth', this.currentValue + 'px');
                    if (o.syncWidth) {
                        this.element.css('width', this.currentValue + 'px');
                    }
                } else {
                    this.element.css('maxWidth', 'none');
                    if (o.syncWidth) {
                        this.element.css('width', '');
                    }
                    this.currentValue = 0;
                }
                this._trigger("resizeMaxWidth", event, {value: this.currentValue});
                break;
            case 's':
                this.currentValue = Math.max(1, this.currentValue);
                this.element.height(this.currentValue);

                this._trigger("resizeHeight", event, {value: this.currentValue});
                break;
        }
    };

    nUINormalSizing.prototype._mouseStop = function (event) {
        var o = this.options;

        this.currentValue = nextend.roundHelper(this.originalValue + this['_parse_movement_' + this.currentHandle].call(this, event));

        switch (this.currentHandle) {
            case 'w':
            case 'e':
                if (this.currentValue <= this.maxWidth) {
                    this.element.css('maxWidth', this.currentValue + 'px');
                    if (o.syncWidth) {
                        this.element.css('width', '');
                    }
                } else {
                    this.element.css('maxWidth', 'none');
                    if (o.syncWidth) {
                        this.element.css('width', '');
                    }
                    this.currentValue = 0;
                }

                this._trigger("stopMaxWidth", event, {value: this.currentValue});
                break;
            case 's':
                this.currentValue = Math.max(1, this.currentValue);
                this.element.height(this.currentValue);

                this._trigger("stopHeight", event, {value: this.currentValue});
                break;
        }

        this.resizing = false;

        $("body").off('.uiNextendNormalSizing')
            .removeClass('n2-ss-normal-sizing-element');

        this.element.removeClass("nui-normal-sizing-resizing");

        nextend.preventMouseUp();
        return false;
    };

    N2Classes.nUIWidgetBase.register('nUINormalSizing');

    return nUINormalSizing;
});
N2D('nUIResizableBar', ['nUIMouse'], function ($, undefined) {
    "use strict";

    /**
     * @memberOf N2Classes
     *
     * @param element
     * @param options
     */
    function nUIResizableBar(element, options) {
        this.element = $(element);

        this.widgetName = this.widgetName || 'nUIResizable';
        this.widgetEventPrefix = "resize";

        this.options = $.extend({
            zIndex: 90,

            // Callbacks
            resize: null,
            start: null,
            stop: null
        }, this.options, options);

        N2Classes.nUIMouse.prototype.constructor.apply(this, arguments);

        this.create();
    }

    nUIResizableBar.prototype = Object.create(N2Classes.nUIMouse.prototype);
    nUIResizableBar.prototype.constructor = nUIResizableBar;

    nUIResizableBar.prototype.create = function () {

        var o = this.options;
        this.element.addClass("nui-resizable");

        this._setupHandles();

        this._mouseInit();

        this._trigger('create', null, {});
    };

    nUIResizableBar.prototype._setupHandles = function () {
        var o = this.options, handle, i, n, hname, axis, that = this;

        this._handles = $();

        n = ["e", "w"];
        this.handles = {};

        for (i = 0; i < n.length; i++) {

            handle = $.trim(n[i]);
            hname = "nui-resizable-" + handle;
            axis = $("<div>")
                .addClass("nui-resizable-handle " + hname)
                .css({zIndex: o.zIndex});

            this.handles[handle] = ".nui-resizable-" + handle;
            this.element.append(axis);
            axis.on({
                "mousedown": $.proxy(function (handleName, e) {
                    this.currentHandle = handleName;
                    this._mouseDown(e);
                }, this, handle)
            });

            this._handles = this._handles.add(axis);
        }

        this._handles.css({
            '-ms-user-select': 'none',
            '-moz-user-select': '-moz-none',
            '-khtml-user-select': 'none',
            '-webkit-user-select': 'none',
            'user-select': 'none'
        });
    };

    nUIResizableBar.prototype._mouseStart = function (event) {

        this.currentData = this.originalData = {
            margin: parseInt(this.element.css(n2const.rtl.marginLeft)),
            width: parseInt(this.element.width())
        };
        this.originalMousePosition = {left: event.pageX};

        var cursor = $(".nui-resizable-" + this.axis).css("cursor");
        $("body").css("cursor", cursor === "auto" ? this.axis + "-resize" : cursor);

        this.element.addClass("nui-resizable-resizing");
        this._trigger("start", event, this.ui());
        return true;
    };

    nUIResizableBar.prototype._mouseDrag = function (event) {
        var dx = (event.pageX - this.originalMousePosition.left) || 0;
        this.currentData = {};

        if (!n2const.rtl.isRtl) {
            if (this.currentHandle === 'e') {
                this.currentData.margin = this.originalData.margin;
                this.currentData.width = Math.max(0, this.originalData.width + dx);
            } else if (this.currentHandle === 'w') {
                this.currentData.margin = Math.max(0, this.originalData.margin + dx);
                this.currentData.width = Math.max(0, this.originalData.width - dx);
            }
        } else {
            if (this.currentHandle === 'e') {
                this.currentData.margin = Math.max(0, this.originalData.margin - dx);
                this.currentData.width = Math.max(0, this.originalData.width + dx);
            } else if (this.currentHandle === 'w') {
                this.currentData.margin = this.originalData.margin;
                this.currentData.width = Math.max(0, this.originalData.width - dx);
            }
        }

        this._trigger("resize", event, this.ui());

        this.element.css(n2const.rtl.marginLeft, this.currentData.margin);
        this.element.css('width', this.currentData.width);
    };

    nUIResizableBar.prototype._mouseStop = function (event) {

        $("body").css("cursor", "auto");

        this.element.removeClass("nui-resizable-resizing");

        this._trigger("stop", event, this.ui());
    };

    nUIResizableBar.prototype.ui = function () {
        return {
            currentData: this.currentData
        };
    };

    N2Classes.nUIWidgetBase.register('nUIResizableBar');

    return nUIResizableBar;
});
N2D('nUIResizable', ['nUIMouse'], function ($, undefined) {
    "use strict";

    /**
     * @memberOf N2Classes
     *
     * @param element
     * @param options
     */
    function nUIResizable(element, options) {
        this.element = $(element);

        this.widgetName = this.widgetName || 'nUIResizable';
        this.widgetEventPrefix = "resize";

        this.options = $.extend({
            alsoResize: false,
            containment: false,
            handles: "e,s,se",
            helper: false,
            maxHeight: null,
            maxWidth: null,
            minHeight: 10,
            minWidth: 10,

            // See #7960
            zIndex: 90,

            // Callbacks
            resize: null,
            start: null,
            stop: null
        }, this.options, options);

        N2Classes.nUIMouse.prototype.constructor.apply(this, arguments);

        this.create();
    }

    nUIResizable.prototype = Object.create(N2Classes.nUIMouse.prototype);
    nUIResizable.prototype.constructor = nUIResizable;

    nUIResizable.plugins = {};

    nUIResizable.prototype._num = function (value) {
        return parseFloat(value) || 0;
    };

    nUIResizable.prototype._isNumber = function (value) {
        return !isNaN(parseFloat(value));
    };

    nUIResizable.prototype._hasScroll = function (el, a) {

        if ($(el).css("overflow") === "hidden") {
            return false;
        }

        var scroll = (a && a === "left") ? "scrollLeft" : "scrollTop",
            has = false;

        if (el[scroll] > 0) {
            return true;
        }

        // TODO: determine which cases actually cause this to happen
        // if the element doesn't have the scroll set, see if it's possible to
        // set the scroll
        el[scroll] = 1;
        has = (el[scroll] > 0);
        el[scroll] = 0;
        return has;
    };

    nUIResizable.prototype.create = function () {

        var o = this.options;
        this.element.addClass("nui-resizable");

        $.extend(this, {
            originalElement: this.element,
            _helper: o.helper ? o.helper || "nui-resizable-helper" : null
        });

        this._setupHandles();

        this._mouseInit();

        this._trigger('create', null, {});
    };

    nUIResizable.prototype._setupHandles = function () {
        var o = this.options, handle, i, n, hname, axis, that = this;
        this.handles = o.handles ||
            (!$(".nui-resizable-handle", this.element).length ?
                "e,s,se" : {
                    n: ".nui-resizable-n",
                    e: ".nui-resizable-e",
                    s: ".nui-resizable-s",
                    w: ".nui-resizable-w",
                    se: ".nui-resizable-se",
                    sw: ".nui-resizable-sw",
                    ne: ".nui-resizable-ne",
                    nw: ".nui-resizable-nw"
                });

        this._handles = $();
        if (this.handles.constructor === String) {

            if (this.handles === "all") {
                this.handles = "n,e,s,w,se,sw,ne,nw";
            }

            n = this.handles.split(",");
            this.handles = {};

            for (i = 0; i < n.length; i++) {

                handle = $.trim(n[i]);
                hname = "nui-resizable-" + handle;
                axis = $("<div>")
                    .addClass("nui-resizable-handle " + hname);

                axis.css({zIndex: o.zIndex});

                this.handles[handle] = ".nui-resizable-" + handle;
                this.element.append(axis);
            }

        }

        this._renderAxis = function () {

            var i;

            for (i in this.handles) {

                if (this.handles[i].constructor === String) {
                    this.handles[i] = this.element.children(this.handles[i]).first().show();
                } else if (this.handles[i].jquery || this.handles[i].nodeType) {
                    this.handles[i] = $(this.handles[i]);
                    this._on(this.handles[i], {"mousedown": that._mouseDown});
                }

                this._handles = this._handles.add(this.handles[i]);
            }
        };

        // TODO: make renderAxis a prototype function
        this._renderAxis(this.element);

        this._handles = this._handles.add(this.element.find(".nui-resizable-handle"));
        this._handles.addClass('n2-unselectable');

        this._handles.on("mouseover", function () {
            if (!that.resizing) {
                if (this.className) {
                    axis = this.className.match(/nui-resizable-(se|sw|ne|nw|n|e|s|w)/i);
                }
                that.axis = axis && axis[1] ? axis[1] : "se";
            }
        });
    };

    nUIResizable.prototype._mouseCapture = function (event) {
        var i, handle,
            capture = false;

        for (i in this.handles) {
            handle = $(this.handles[i])[0];
            if (handle === event.target || $.contains(handle, event.target)) {
                capture = true;
            }
        }

        return !this.options.disabled && capture;
    };

    nUIResizable.prototype._removeHandles = function () {
        this._handles.remove();
    };

    nUIResizable.prototype._mouseStart = function (event) {

        var position = this.element.position();
        this.element.css({
            left: position.left,
            top: position.top,
            right: 'auto',
            bottom: 'auto'
        });

        var curleft, curtop, cursor,
            o = this.options,
            el = this.element;

        this.resizing = true;

        this._renderProxy();

        curleft = this._num(this.helper.css("left"));
        curtop = this._num(this.helper.css("top"));

        if (o.containment) {
            curleft += $(o.containment).scrollLeft() || 0;
            curtop += $(o.containment).scrollTop() || 0;
        }

        this.offset = this.helper.offset();
        this.position = {left: curleft, top: curtop};

        this.size = this._helper ? {
            width: this.helper.width(),
            height: this.helper.height()
        } : {
            width: el.width(),
            height: el.height()
        };

        this.originalSize = this._helper ? {
            width: el.outerWidth(),
            height: el.outerHeight()
        } : {
            width: el.width(),
            height: el.height()
        };

        this.sizeDiff = {
            width: el.outerWidth() - el.width(),
            height: el.outerHeight() - el.height()
        };

        this.originalPosition = {left: curleft, top: curtop};
        this.originalMousePosition = {left: event.pageX, top: event.pageY};

        cursor = $(".nui-resizable-" + this.axis).css("cursor");
        $("body").css("cursor", cursor === "auto" ? this.axis + "-resize" : cursor);

        this.element.addClass("nui-resizable-resizing");
        this._propagate("start", event);
        return true;
    };

    nUIResizable.prototype._mouseDrag = function (event) {

        var data, props,
            smp = this.originalMousePosition,
            a = this.axis,
            dx = (event.pageX - smp.left) || 0,
            dy = (event.pageY - smp.top) || 0,
            trigger = this._change[a];

        this._updatePrevProperties();

        if (!trigger) {
            return false;
        }

        data = trigger.apply(this, [event, dx, dy]);

        this._updateVirtualBoundaries();

        data = this._respectSize(data, event);

        this._updateCache(data);

        this._propagate("resize", event);

        props = this._applyChanges();

        if (!$.isEmptyObject(props)) {
            this._updatePrevProperties();
            this._trigger("resize", event, this.ui());
            this._applyChanges();
        }

        return false;
    };

    nUIResizable.prototype._mouseStop = function (event) {

        this.resizing = false;
        var s, left, top, that = this;

        if (this._helper) {

            s = {
                width: that.helper.width(),
                height: that.helper.height()
            };
            left = (parseFloat(that.element.css("left")) +
                (that.position.left - that.originalPosition.left)) || null;
            top = (parseFloat(that.element.css("top")) +
                (that.position.top - that.originalPosition.top)) || null;

            this.element.css($.extend(s, {top: top, left: left}));


            that.helper.height(that.size.height);
            that.helper.width(that.size.width);

        }

        $("body").css("cursor", "auto");

        this.element.removeClass("nui-resizable-resizing");

        this._propagate("stop", event);

        if (this._helper) {
            this.helper.remove();
        }

        return false;

    };

    nUIResizable.prototype._updatePrevProperties = function () {
        this.prevPosition = {
            top: this.position.top,
            left: this.position.left
        };
        this.prevSize = {
            width: this.size.width,
            height: this.size.height
        };
    };

    nUIResizable.prototype._applyChanges = function () {
        var props = {};

        if (this.position.top !== this.prevPosition.top) {
            props.top = this.position.top + "px";
        }
        if (this.position.left !== this.prevPosition.left) {
            props.left = this.position.left + "px";
        }
        if (this.size.width !== this.prevSize.width) {
            props.width = this.size.width + "px";
        }
        if (this.size.height !== this.prevSize.height) {
            props.height = this.size.height + "px";
        }

        this.helper.css(props);

        return props;
    };

    nUIResizable.prototype._updateVirtualBoundaries = function () {
        var b,
            o = this.options;

        b = {
            minWidth: this._isNumber(o.minWidth) ? o.minWidth : 0,
            maxWidth: this._isNumber(o.maxWidth) ? o.maxWidth : Infinity,
            minHeight: this._isNumber(o.minHeight) ? o.minHeight : 0,
            maxHeight: this._isNumber(o.maxHeight) ? o.maxHeight : Infinity
        };

        this._vBoundaries = b;
    };

    nUIResizable.prototype._updateCache = function (data) {
        this.offset = this.helper.offset();
        if (this._isNumber(data.left)) {
            this.position.left = data.left;
        }
        if (this._isNumber(data.top)) {
            this.position.top = data.top;
        }
        if (this._isNumber(data.height)) {
            this.size.height = data.height;
        }
        if (this._isNumber(data.width)) {
            this.size.width = data.width;
        }
    };

    nUIResizable.prototype._respectSize = function (data) {

        var o = this._vBoundaries,
            a = this.axis,
            ismaxw = this._isNumber(data.width) && o.maxWidth && (o.maxWidth < data.width),
            ismaxh = this._isNumber(data.height) && o.maxHeight && (o.maxHeight < data.height),
            isminw = this._isNumber(data.width) && o.minWidth && (o.minWidth > data.width),
            isminh = this._isNumber(data.height) && o.minHeight && (o.minHeight > data.height),
            dw = this.originalPosition.left + this.originalSize.width,
            dh = this.originalPosition.top + this.originalSize.height,
            cw = /sw|nw|w/.test(a), ch = /nw|ne|n/.test(a);
        if (isminw) {
            data.width = o.minWidth;
        }
        if (isminh) {
            data.height = o.minHeight;
        }
        if (ismaxw) {
            data.width = o.maxWidth;
        }
        if (ismaxh) {
            data.height = o.maxHeight;
        }

        if (isminw && cw) {
            data.left = dw - o.minWidth;
        }
        if (ismaxw && cw) {
            data.left = dw - o.maxWidth;
        }
        if (isminh && ch) {
            data.top = dh - o.minHeight;
        }
        if (ismaxh && ch) {
            data.top = dh - o.maxHeight;
        }

        // Fixing jump error on top/left - bug #2330
        if (!data.width && !data.height && !data.left && data.top) {
            data.top = null;
        } else if (!data.width && !data.height && !data.top && data.left) {
            data.left = null;
        }

        return data;
    };

    nUIResizable.prototype._renderProxy = function () {

        var el = this.element, o = this.options;
        this.elementOffset = el.offset();

        if (this._helper) {

            this.helper = this.helper || $("<div style='overflow:hidden;'></div>")
                .addClass(this._helper);
            this.helper.css({
                width: this.element.outerWidth(),
                height: this.element.outerHeight(),
                position: "absolute",
                left: this.elementOffset.left + "px",
                top: this.elementOffset.top + "px",
                zIndex: ++o.zIndex //TODO: Don't modify option
            });

            this.helper
                .addClass('n2-unselectable')
                .appendTo("body");

        } else {
            this.helper = this.element;
        }

    };

    nUIResizable.prototype._change = {
        e: function (event, dx) {
            return {width: this.originalSize.width + dx};
        },
        w: function (event, dx) {
            var cs = this.originalSize, sp = this.originalPosition;
            return {left: sp.left + dx, width: cs.width - dx};
        },
        n: function (event, dx, dy) {
            var cs = this.originalSize, sp = this.originalPosition;
            return {top: sp.top + dy, height: cs.height - dy};
        },
        s: function (event, dx, dy) {
            return {height: this.originalSize.height + dy};
        },
        se: function (event, dx, dy) {
            return $.extend(this._change.s.apply(this, arguments),
                this._change.e.apply(this, [event, dx, dy]));
        },
        sw: function (event, dx, dy) {
            return $.extend(this._change.s.apply(this, arguments),
                this._change.w.apply(this, [event, dx, dy]));
        },
        ne: function (event, dx, dy) {
            return $.extend(this._change.n.apply(this, arguments),
                this._change.e.apply(this, [event, dx, dy]));
        },
        nw: function (event, dx, dy) {
            return $.extend(this._change.n.apply(this, arguments),
                this._change.w.apply(this, [event, dx, dy]));
        }
    };

    nUIResizable.prototype._propagate = function (n, event) {
        this.callPlugin(n, [event, this.ui()]);
        (n !== "resize" && this._trigger(n, event, this.ui()));
    };

    nUIResizable.prototype.ui = function () {
        return {
            originalElement: this.originalElement,
            element: this.element,
            helper: this.helper,
            position: this.position,
            size: this.size,
            originalSize: this.originalSize,
            originalPosition: this.originalPosition,
            axis: this.axis
        };
    };


    nUIResizable.prototype._destroy = function () {
        this._mouseDestroy();

        this.element
            .removeClass("nui-resizable")
            .removeData(this.widgetName);

        for (var k in this.handles) {
            this.handles[k].remove();
        }

        return this;
    };

    N2Classes.nUIWidgetBase.addPlugin(nUIResizable, "smartguides", {
        start: function (event, ui) {
            var i = $(this).data("nUIResizable"), o = i.options;
            i.gridH = $('<div class="n2-grid n2-grid-h"></div>').appendTo(o._containment);
            i.gridV = $('<div class="n2-grid n2-grid-v"></div>').appendTo(o._containment);
            i.gridH2 = $('<div class="n2-grid n2-grid-h"></div>').appendTo(o._containment);
            i.gridV2 = $('<div class="n2-grid n2-grid-v"></div>').appendTo(o._containment);
            i.elements = [];
            if (typeof o.smartguides == 'function') {

                var guides = o.smartguides();
                if (guides) {
                    var containmentOffset = o._containment.offset();
                    guides.each(function () {
                        var $t = $(this);
                        var $o = $t.offset();
                        if (this != i.element[0]) i.elements.push({
                            item: this,
                            width: $t.outerWidth(),
                            height: $t.outerHeight(),
                            top: Math.round($o.top - containmentOffset.top),
                            left: Math.round($o.left - containmentOffset.left)
                        });
                    });
                    i.elements.push({
                        item: o._containment,
                        width: o._containment.width(), height: o._containment.height(),
                        top: 0, left: 0
                    });
                }
            }
        },
        stop: function (event, ui) {
            var i = $(this).data("nUIResizable");
            i.gridH.remove();
            i.gridV.remove();
            i.gridH2.remove();
            i.gridV2.remove();
        },
        resize: function (event, ui) {
            var inst = $(this).data("nUIResizable"), o = inst.options;
            var d = o.tolerance;
            inst.gridV.css({"display": "none"});
            inst.gridH.css({"display": "none"});
            inst.gridV2.css({"display": "none"});
            inst.gridH2.css({"display": "none"});


            var container = inst.elements[inst.elements.length - 1];

            function setGridV(left) {
                inst.gridV.css({left: Math.min(left, container.width - 1), display: "block"});
            };

            function setGridV2(left) {
                inst.gridV2.css({left: Math.min(left, container.width - 1), display: "block"});
            };

            function setGridH(top) {
                inst.gridH.css({top: Math.min(top, container.height - 1), display: "block"});
            };

            function setGridH2(top) {
                inst.gridH2.css({top: Math.min(top, container.height - 1), display: "block"});
            };

            var ctrlKey = event.ctrlKey || event.metaKey,
                altKey = event.altKey;
            if (ctrlKey && altKey) {
                return;
            }

            var x1 = ui.position.left, x2 = x1 + ui.size.width,
                y1 = ui.position.top, y2 = y1 + ui.size.height;
            for (var i = inst.elements.length - 1; i >= 0; i--) {
                var l = inst.elements[i].left, r = l + inst.elements[i].width,
                    t = inst.elements[i].top, b = t + inst.elements[i].height;

                if (!ctrlKey) {
                    var hc = (l + r) / 2;

                    if (Math.abs(l - x2) <= d) {
                        ui.size.width = l - ui.position.left;
                        setGridV(ui.position.left + ui.size.width);
                    } else if (Math.abs(l - x1) <= d) {
                        var diff = ui.position.left - l;
                        ui.position.left = l;
                        ui.size.width += diff;
                        setGridV(ui.position.left);
                    } else if (Math.abs(hc - x1) <= d) {
                        var diff = ui.position.left - hc;
                        ui.position.left = hc;
                        ui.size.width += diff;
                        setGridV(ui.position.left);
                    }

                    if (Math.abs(r - x1) <= d) {
                        var diff = ui.position.left - r;
                        ui.position.left = r;
                        ui.size.width += diff;
                        setGridV2(ui.position.left);
                    } else if (Math.abs(r - x2) <= d) {
                        ui.size.width = r - ui.position.left;
                        setGridV2(ui.position.left + ui.size.width);
                    } else if (Math.abs(hc - x2) <= d) {
                        ui.size.width = hc - ui.position.left;
                        setGridV2(ui.position.left + ui.size.width);
                    }
                }

                if (!altKey) {
                    var vc = (t + b) / 2;

                    if (Math.abs(t - y2) <= d) {
                        ui.size.height = t - ui.position.top;
                        setGridH(t);
                    } else if (Math.abs(t - y1) <= d) {
                        var diff = ui.position.top - t;
                        ui.position.top = t;
                        ui.size.height += diff;
                        setGridH(ui.position.top);
                    } else if (Math.abs(vc - y1) <= d) {
                        var diff = ui.position.top - vc;
                        ui.position.top = vc;
                        ui.size.height += diff;
                        setGridH(ui.position.top);
                    }

                    if (Math.abs(b - y1) <= d) {
                        var diff = ui.position.top - b;
                        ui.position.top = b;
                        ui.size.height += diff;
                        setGridH2(ui.position.top);
                    } else if (Math.abs(b - y2) <= d) {
                        ui.size.height = b - ui.position.top;
                        setGridH2(ui.position.top + ui.size.height);
                    } else if (Math.abs(vc - y2) <= d) {
                        ui.size.height = vc - ui.position.top;
                        setGridH2(ui.position.top + ui.size.height);
                    }
                }
            }
        }
    });

    N2Classes.nUIWidgetBase.register('nUIResizable');

    return nUIResizable;
});
N2D('nUISlider', ['nUIMouse'], function ($, undefined) {
    "use strict";

    /**
     * @memberOf N2Classes
     *
     * @constructor
     * @augments nUIMouse
     * @this nUISlider
     */
    function nUISlider(element, options) {
        this.element = $(element);

        this.widgetName = this.widgetName || 'nUISlider';
        this.widgetEventPrefix = "slide";

        this.options = $.extend({
            min: 0,
            max: 100,
            step: 1,
            value: 0,

            // Callbacks
            change: null,
            slide: null,
            start: null,
            stop: null
        }, this.options, options);

        N2Classes.nUIMouse.prototype.constructor.apply(this, arguments);

        this.create();
    }

    nUISlider.prototype = Object.create(N2Classes.nUIMouse.prototype);
    nUISlider.prototype.constructor = nUISlider;


    N2Classes.nUIWidgetBase.register('nUISlider');

    nUISlider.prototype.create = function () {
        this._mouseSliding = false;
        this._mouseInit();
        this._calculateNewMax();

        this.element.addClass("nui-slider nui-slider-horizontal");

        this._refresh();
    };

    nUISlider.prototype.doAction = function (action) {
        N2Classes.nUIWidgetBase.prototype.doAction.apply(this, arguments);

        switch (action) {
            case 'value':
                return this._value();
        }
    };

    nUISlider.prototype.setOption = function (key, value) {
        N2Classes.nUIWidgetBase.prototype.setOption.call(this, key, value);

        switch (key) {
            case "value":
                this._refreshValue();
                this._change(null, 0);
                break;
            case "step":
            case "min":
            case "max":
                this._calculateNewMax();
                this._refreshValue();
                break;
        }
    };


    nUISlider.prototype._refresh = function () {
        this._createHandle();
        this._refreshValue();
    };

    nUISlider.prototype._createHandle = function () {
        var existingHandle = this.element.find(".nui-slider-handle");

        if (existingHandle.length) {
            this.handle = existingHandle;
        } else {
            this.handle = $("<span tabindex='0'></span>");
        }

        this.handle.attr("tabIndex", 0)
            .addClass("nui-slider-handle")
            .appendTo(this.element);
    };

    nUISlider.prototype._mouseCapture = function (event) {
        var position, normValue, distance, handle = this.handle, allowed, offset, mouseOverHandle,
            o = this.options;

        if (o.disabled) {
            return false;
        }

        this.elementSize = {
            width: this.element.outerWidth(),
            height: this.element.outerHeight()
        };
        this.elementOffset = this.element.offset();

        position = {x: event.pageX, y: event.pageY};
        normValue = this._normValueFromMouse(position);

        allowed = this._start(event);
        if (allowed === false) {
            return false;
        }
        this._mouseSliding = true;

        handle.trigger("focus");

        this._slide(event, normValue);

        return true;
    };

    nUISlider.prototype._mouseStart = function () {
        this.lastValue = Number.MAX_VALUE;
        return true;
    };

    nUISlider.prototype._mouseDrag = function (event) {
        var position = {x: event.pageX, y: event.pageY},
            normValue = this._normValueFromMouse(position);

        if (this.lastValue != normValue) {
            this._slide(event, normValue);
            this.lastValue = normValue;
        }

        return false;
    };

    nUISlider.prototype._mouseStop = function (event) {
        this._mouseSliding = false;

        this._stop(event);
        this._change(event);

        return false;
    };

    nUISlider.prototype._normValueFromMouse = function (position) {
        var pixelTotal,
            pixelMouse,
            percentMouse,
            valueTotal,
            valueMouse;

        pixelTotal = this.elementSize.width;
        pixelMouse = position.x - this.elementOffset.left;

        percentMouse = ( pixelMouse / pixelTotal );
        if (percentMouse > 1) {
            percentMouse = 1;
        }
        if (percentMouse < 0) {
            percentMouse = 0;
        }

        valueTotal = this._valueMax() - this._valueMin();
        valueMouse = this._valueMin() + percentMouse * valueTotal;

        return this._trimAlignValue(valueMouse);
    };

    nUISlider.prototype._trimAlignValue = function (val) {
        if (val <= this._valueMin()) {
            return this._valueMin();
        }
        if (val >= this._valueMax()) {
            return this._valueMax();
        }
        var step = ( this.options.step > 0 ) ? this.options.step : 1,
            valModStep = ( val - this._valueMin() ) % step,
            alignValue = val - valModStep;

        if (Math.abs(valModStep) * 2 >= step) {
            alignValue += ( valModStep > 0 ) ? step : ( -step );
        }

        // Since JavaScript has problems with large floats, round
        // the final value to 5 digits after the decimal point (see #4124)
        return parseFloat(alignValue.toFixed(5));
    };

    nUISlider.prototype._calculateNewMax = function () {
        var max = this.options.max,
            min = this._valueMin(),
            step = this.options.step,
            aboveMin = Math.round(( max - min ) / step) * step;
        max = aboveMin + min;
        if (max > this.options.max) {

            //If max is not divisible by step, rounding off may increase its value
            max -= step;
        }
        this.max = parseFloat(max.toFixed(this._precision()));
    };

    nUISlider.prototype._precision = function () {
        var precision = this._precisionOf(this.options.step);
        if (this.options.min !== null) {
            precision = Math.max(precision, this._precisionOf(this.options.min));
        }
        return precision;
    };

    nUISlider.prototype._precisionOf = function (num) {
        var str = num.toString(),
            decimal = str.indexOf(".");
        return decimal === -1 ? 0 : str.length - decimal - 1;
    };

    nUISlider.prototype._change = function (event) {
        if (!this._mouseSliding) {
            this._trigger("change", event, this._uiHash());
        }
    };

    nUISlider.prototype.value = function (newValue) {
        if (arguments.length) {
            this.options.value = this._trimAlignValue(newValue);
            this._refreshValue();
            this._change(null, 0);
            return;
        }

        return this._value();
    };

    //internal value getter
    // _value() returns value trimmed by min and max, aligned by step
    nUISlider.prototype._value = function () {
        var val = this.options.value;
        val = this._trimAlignValue(val);

        return val;
    };

    nUISlider.prototype._valueMin = function () {
        return this.options.min;
    };

    nUISlider.prototype._valueMax = function () {
        return this.max;
    };

    nUISlider.prototype._refreshValue = function () {
        var value = this.value(),
            valueMin = this._valueMin(),
            valueMax = this._valueMax(),
            valPercent = ( valueMax !== valueMin ) ? ( value - valueMin ) / ( valueMax - valueMin ) * 100 : 0;
        this.handle.css('left', valPercent + "%");
    };


    nUISlider.prototype._uiHash = function (value) {
        return {
            handle: this.handle[0],
            value: value !== undefined ? value : this.value()
        };
    };

    nUISlider.prototype._start = function (event) {
        return this._trigger("start", event, this._uiHash());
    };

    nUISlider.prototype._slide = function (event, newVal) {
        var allowed,
            currentValue = this.value();

        if (newVal === currentValue) {
            return;
        }

        allowed = this._trigger("slide", event, this._uiHash(newVal));

        // A slide can be canceled by returning false from the slide callback
        if (allowed === false) {
            return;
        }

        this.value(newVal);
    };

    nUISlider.prototype._stop = function (event) {
        this._trigger("stop", event, this._uiHash());
    };

    return nUISlider;
});
N2D('nUISortable', ['nUIMouse'], function ($, undefined) {
    "use strict";

    /**
     * @memberOf N2Classes
     *
     * @constructor
     * @augments nUIMouse
     */
    function nUISortable(element, options) {
        this.element = $(element);

        this.widgetName = this.widgetName || 'nUISortable';
        this.widgetEventPrefix = "sortable";

        this.options = $.extend({
            items: '> *',
            handle: '',
            placeholder: false,
            helper: 'original',
            forcePlaceholderSize: false,
            forceHelperSize: false,
            axis: false,
            droppables: false
        }, this.options, options);

        this.isOver = false;

        N2Classes.nUIMouse.prototype.constructor.apply(this, arguments);

        this.create();
    }

    nUISortable.prototype = Object.create(N2Classes.nUIMouse.prototype);
    nUISortable.prototype.constructor = nUISortable;

    nUISortable.prototype.create = function () {
        this._mouseInit();
    };

    nUISortable.prototype._mouseCapture = function (e) {
        if (this.options.disabled) {
            return false;
        }

        var currentItem = null,
            $target = $(e.target);

        if (this.options.handle !== '') {
            var handles = this.element.find(this.options.items + ' ' + this.options.handle);
            if (handles.index($target) === -1 && !handles.has($target).length) {
                //Item not dragged by the handle
                return false;
            }
        }

        var items = this.element.find(this.options.items);
        if (items.index($target) !== -1) {
            currentItem = $target;
        } else {
            currentItem = items.has($target);
        }

        if (!currentItem.length) {
            return false;
        }

        this.currentItem = currentItem;

        return true;
    };

    nUISortable.prototype._mouseStart = function (e) {

        this._trigger('beforestart', e, {
            currentItem: this.currentItem
        });

        this.context = {
            e: e,
            original: {
                pageX: e.pageX,
                pageY: e.pageY,
                clientX: e.clientX,
                clientY: e.clientY,
                elementBCR: this.element[0].getBoundingClientRect(),
                currentItemBCR: this.currentItem[0].getBoundingClientRect()
            },
            offsetShift: {
                top: 0,
                left: 0
            },
            scrollCB: $.proxy(this._mouseScroll, this)
        };

        this.element.addClass('n2-ui-sortable-in-progress');

        this._cacheItems();

        this.placeholder = $('<div/>')
            .addClass(this.options.placeholder || this.currentItem[0].className);

        var size = {
            width: this.currentItem.width(),
            height: this.currentItem.height()
        };

        if (this.options.helper === 'clone') {
            this.helper = this.currentItem.clone();
        } else if (this.options.helper === 'clone_hide') {
            this.helper = this.currentItem.clone();
            this.currentItem.css('display', 'none');
        } else {
            this.helper = this.currentItem
        }

        if (this.options.forceHelperSize) {
            this.helper.css(size);
        }

        if (this.options.forcePlaceholderSize) {
            this.placeholder.css(size);
        }

        this.helper.addClass('n2-ui-sortable-helper')
            .css({
                position: 'absolute',
                zIndex: 1000
            })
            .appendTo(this.element);

        this._trigger('start', e, this.ui());

        /**
         * Trigger mousemove event on scroll to update the helper position
         */
        window.addEventListener('scroll', this.context.scrollCB, {
            capture: true,
            passive: true
        });
    };

    nUISortable.prototype._mouseDrag = function (e) {
        this.context.e = e;

        var elementBDR = this.element[0].getBoundingClientRect(),
            helperPosition = {};

        if (!this.options.axis || this.options.axis === 'x') {

            var modifierX = elementBDR.left + this.context.original.clientX - e.clientX;
            this.context.offsetShift.left = e.pageX - this.context.original.pageX - this.context.original.elementBCR.left + modifierX;

            helperPosition.left = this.context.original.currentItemBCR.left - modifierX;
        }

        if (!this.options.axis || this.options.axis === 'y') {

            var modifierY = elementBDR.top + this.context.original.clientY - e.clientY;
            this.context.offsetShift.top = e.pageY - this.context.original.pageY - this.context.original.elementBCR.top + modifierY;

            helperPosition.top = this.context.original.currentItemBCR.top - modifierY;
        }

        this.helper.css(helperPosition);

        var closestData = this._findClosestItem(e),
            nearestItem = closestData[1] === 'before' ? this.items[Math.max(0, closestData[2] - 1)] : this.items[Math.min(this.items.length - 1, closestData[2])];

        if (this.options.helper === 'clone' && (closestData[0].is(this.currentItem) || $(nearestItem).is(this.currentItem))) {
            this.placeholder.detach();
        } else {

            this.positionPlaceholder(closestData);
        }

        if (this.options.droppables) {
            if (closestData[1] !== 'over') {
                if (this.isOver) {
                    this._trigger('out', e, this.ui());
                    this.isOver = false;
                }
            } else {
                if (this.isOver && !this.isOver.is(closestData[0])) {
                    this._trigger('out', e, this.ui());
                    this.isOver = false;
                }

                if (!this.isOver) {
                    this.isOver = closestData[0];
                    this._trigger('over', e, this.ui());
                }
            }
        }
    };

    nUISortable.prototype.positionPlaceholder = function (closestData) {
        switch (closestData[1]) {
            case 'before':
                this.placeholder.insertBefore(closestData[0]);
                break;
            case 'after':
                this.placeholder.insertAfter(closestData[0]);
                break;
            case 'over':
                this.placeholder.detach();
                break;
        }
    };

    nUISortable.prototype._mouseStop = function (e) {
        this.context.e = e;

        this._trigger('beforestop', e, this.ui());

        window.removeEventListener('scroll', this.context.scrollCB, {
            capture: true,
            passive: true
        });

        this.placeholder.remove();

        var closestData = this._findClosestItem(e);
        switch (closestData[1]) {
            case 'before':
                this.currentItem.insertBefore(closestData[0]);
                break;
            case 'after':
                this.currentItem.insertAfter(closestData[0]);
                break;
        }

        this.helper
            .removeClass('n2-ui-sortable-helper')
            .css({
                position: '',
                zIndex: '',
                left: '',
                top: ''
            });

        if (this.options.helper === 'clone' || this.options.helper === 'clone_hide') {
            this.helper.remove();
        }
        if (this.options.helper === 'clone_hide') {
            this.currentItem.css('display', '');
        }

        this.element.removeClass('n2-ui-sortable-in-progress');

        if (closestData[1] === 'over') {
            this._trigger('drop', e, this.ui());
            this._trigger('out', e, this.ui());
        } else {
            this._trigger('stop', e, this.ui());
        }

        this.currentItem = null;

    };

    nUISortable.prototype._findClosestItem = function (e) {
        var distance = Number.MAX_VALUE,
            left = e.pageX - this.context.offsetShift.left,
            top = e.pageY - this.context.offsetShift.top,
            closestItem, closestItemSide, closestItemIndex;

        for (var i = 0; i < this.itemsData.length; i++) {
            var item = this.itemsData[i];
            for (var k in item.side) {
                var localDistance = Math.sqrt(Math.pow((left - item.side[k].left) / item.width, 2) + Math.pow((top - item.side[k].top) / item.height, 2));
                if (localDistance < distance) {
                    distance = localDistance;
                    closestItem = item.item;

                    closestItemSide = k;
                    if (k === 'after') {
                        closestItemIndex = i + 1;
                    } else if (k === 'before') {
                        closestItemIndex = i;
                    }
                }
            }
        }
        return [closestItem, closestItemSide, closestItemIndex];
    };

    nUISortable.prototype.getItems = function () {
        return this.element.find(this.options.items);
    };

    nUISortable.prototype._cacheItems = function () {
        this.items = this.getItems();

        this.itemsData = [];
        for (var i = 0; i < this.items.length; i++) {

            if (this.options.helper === 'original' && this.items[i] === this.currentItem[0]) {
                continue;
            }

            var offset = this.items.eq(i).offset(),
                width = this.items.eq(i).width(),
                height = this.items.eq(i).height(),
                side = {};

            if (this.options.droppables && !this.currentItem.hasClass(this.options.droppables) && this.items.eq(i).hasClass(this.options.droppables)) {
                side.before = {
                    left: offset.left + width / 6,
                    top: offset.top + height / 6
                };
                side.over = {
                    left: offset.left + 3 * width / 6,
                    top: offset.top + 3 * height / 6
                };
                side.after = {
                    left: offset.left + 5 * width / 6,
                    top: offset.top + 5 * height / 6
                };
            } else {
                side.before = {
                    left: offset.left + width / 4,
                    top: offset.top + height / 4
                };
                side.after = {
                    left: offset.left + 3 * width / 4,
                    top: offset.top + 3 * height / 4
                };
            }

            if (window.n2const.isRTL()) {
                var after = side.after;
                side.after = side.before;
                side.before = after;
            }

            this.itemsData.push({
                item: this.items.eq(i),
                side: side,
                width: width,
                height: height
            });
        }
    };

    nUISortable.prototype._mouseScroll = function () {
        var e = this.context.e;
        document.dispatchEvent(new MouseEvent('mousemove', {
            clientX: e.clientX,
            clientY: e.clientY
        }));
    };

    nUISortable.prototype.ui = function () {
        return {
            placeholder: this.placeholder,
            helper: this.helper,
            item: this.currentItem,
            droppable: this.isOver
        };
    };

    N2Classes.nUIWidgetBase.register('nUISortable');

    return nUISortable;
});
N2D('nUISortableRow', ['nUISortable'], function ($, undefined) {
    "use strict";

    /**
     * @memberOf N2Classes
     *
     * @param element
     * @param options
     */
    function nUISortableRow(element, options) {

        N2Classes.nUISortable.prototype.constructor.apply(this, arguments);

        this.lastPosition = null;
    }


    nUISortableRow.prototype = Object.create(N2Classes.nUISortable.prototype);
    nUISortableRow.prototype.constructor = nUISortableRow;


    nUISortableRow.prototype.getItems = function () {
        var items = this.element.find(this.options.items);

        if (items.eq(0).attr('style').indexOf('order:') !== -1) {
            items.sort(function (a, b) {
                var aOrder = $(a).css('order'),
                    bOrder = $(b).css('order');
                return ((aOrder < bOrder) ? -1 : ((aOrder > bOrder) ? 1 : 0));
            });

            this.hasOrder = true;
        } else {
            this.hasOrder = false;
        }

        this.lastPosition = null;

        return items;
    };


    nUISortableRow.prototype.positionPlaceholder = function (closestData) {
        switch (closestData[1]) {
            case 'before':
                this.placeholder.insertBefore(closestData[0]);
                if (this.hasOrder) {
                    this.placeholder.css('order', closestData[0].css('order') - 1);
                }
                break;
            case 'after':
                this.placeholder.insertAfter(closestData[0]);
                if (this.hasOrder) {
                    this.placeholder.css('order', closestData[0].css('order') + 1);
                }
                break;
            case 'over':
                this.placeholder.detach();
                break;
        }

        this.lastPosition = closestData;
    };

    nUISortableRow.prototype.ui = function () {
        return {
            placeholder: this.placeholder,
            helper: this.helper,
            item: this.currentItem,
            droppable: this.isOver,
            lastPosition: this.lastPosition
        };
    };

    N2Classes.nUIWidgetBase.register('nUISortableRow');

    return nUISortableRow;
});
N2D('nUISpacing', ['nUIMouse'], function ($, undefined) {
    "use strict";

    /**
     * @memberOf N2Classes
     *
     * @constructor
     * @augments nUIMouse
     * @this nUISpacing
     */
    function nUISpacing(element, options) {
        this.element = $(element);

        this.widgetName = this.widgetName || 'nUISpacing';
        this.widgetEventPrefix = "spacing";

        this.options = $.extend({
            handles: '',
            mode: 'padding',
            sync: {
                n: 'padding-top',
                e: 'padding-right',
                s: 'padding-bottom',
                w: 'padding-left'
            },
            syncInv: {
                n: 's',
                e: 'w',
                s: 'n',
                w: 'e'
            },
            side: {
                n: 'top',
                e: 'right',
                s: 'bottom',
                w: 'left'
            },
            size: {
                n: 'height',
                e: 'width',
                s: 'height',
                w: 'width'
            },
            // Callbacks
            drag: null,
            start: null,
            stop: null
        }, this.options, options);

        N2Classes.nUIMouse.prototype.constructor.apply(this, arguments);

        this.create();
    }

    nUISpacing.prototype = Object.create(N2Classes.nUIMouse.prototype);
    nUISpacing.prototype.constructor = nUISpacing;


    nUISpacing.prototype.create = function () {

        this._setupHandles();

        this._mouseInit();
    };


    nUISpacing.prototype._destroy = function () {

        this._mouseDestroy();

        this.element
            .removeData(this.widgetName);

        for (var k in this.handles) {
            this.handles[k].remove();
        }

        return this;
    };

    nUISpacing.prototype._setupHandles = function () {
        var o = this.options, handle, i, n, hname, axis;

        n = "n,e,s,w".split(",");
        this.handles = {};

        for (i = 0; i < n.length; i++) {

            handle = $.trim(n[i]);
            hname = "nui-spacing-" + handle;
            this.handles[handle] = axis = $("<div>")
                .addClass("nui-spacing-handle nui-spacing-handle-" + o.mode + " nui-spacing-handle " + hname)
                .addClass('n2-unselectable')
                .on('mousedown', $.proxy(this._mouseDown, this))
                .appendTo(this.element);

            nextend.tooltip.addElement(this.handles[handle], N2Classes.StringHelper.capitalize(o.mode) + ' ' + o.side[handle]);

        }
    };

    nUISpacing.prototype._removeHandles = function () {
        this.element.find("> .nui-spacing-handle").remove();
    };

    nUISpacing.prototype._parse_movement_n = function (e) {
        return e.pageY - this.originalMousePosition.top;
    };

    nUISpacing.prototype._parse_movement_w = function (e) {
        return e.pageX - this.originalMousePosition.left;
    };

    nUISpacing.prototype._parse_movement_s = function (e) {
        return e.pageY - this.originalMousePosition.top;
    };

    nUISpacing.prototype._parse_movement_e = function (e) {
        return this.originalMousePosition.left - e.pageX;
    };

    nUISpacing.prototype._mouseCapture = function (e) {
        var i, handle,
            capture = false;

        for (i in this.handles) {
            handle = $(this.handles[i])[0];
            if (handle === e.target || $.contains(handle, e.target)) {
                capture = true;
            }
        }

        return !this.options.disabled && capture;
    };

    nUISpacing.prototype._mouseStart = function (e) {

        this.wasShiftPressed = false;
        var handle;
        for (var d in this.handles) {
            handle = this.handles[d][0];
            if (handle === e.target || $.contains(handle, e.target)) {
                this.direction = d;
                break;
            }
        }
        this.syncProperty = this.options.sync[this.direction];
        this.originalValue = parseInt(this.element.css(this.syncProperty));

        this.invSyncProperty = this.options.sync[this.options.syncInv[this.direction]];
        this.invOriginalValue = parseInt(this.element.css(this.invSyncProperty));

        this.resizing = true;

        this.originalMousePosition = {left: e.pageX, top: e.pageY};
        this.currentValue = this.originalValue;

        this.handles[this.direction].addClass('nui-spacing-under-spacing');

        this.element.addClass("nui-spacing-resizing");
        $("body")
            .on('keydown.' + this.widgetEventPrefix, $.proxy(this._keyDown, this))
            .on('keyup.' + this.widgetEventPrefix, $.proxy(this._keyUp, this))
            .addClass('n2-ss-spacing-element');

        this._trigger("start", e, this.ui());
        return true;
    };

    nUISpacing.prototype._keyDown = function (e) {
        if (e.shiftKey && !this.wasShiftPressed) {
            this.wasShiftPressed = true;
            this.element.css(this.invSyncProperty, this.currentValue);
            this.handles[this.options.syncInv[this.direction]].css(this.options.size[this.options.syncInv[this.direction]], this.currentValue);
            this._trigger("spacing", e, this.ui());
        }
    };

    nUISpacing.prototype._keyUp = function (e) {
        if (!e.shiftKey && this.wasShiftPressed) {
            this.wasShiftPressed = false;
            this.element.css(this.invSyncProperty, this.invOriginalValue);
            this.handles[this.options.syncInv[this.direction]].css(this.options.size[this.options.syncInv[this.direction]], '');
            this._trigger("spacing", e, this.ui());
        }
    };

    nUISpacing.prototype._mouseDrag = function (e) {

        this.movement = this['_parse_movement_' + this.direction].call(this, e);

        this.currentValue = nextend.roundHelper(this.originalValue + this.movement);

        if (this.options.mode == 'padding') {
            this.currentValue = Math.max(0, this.currentValue);
        }

        this.element.css(this.syncProperty, this.currentValue);
        this.handles[this.direction].css(this.options.size[this.direction], this.currentValue);
        if (e.shiftKey) {
            this.wasShiftPressed = true;
            this.element.css(this.invSyncProperty, this.currentValue);
            this.handles[this.options.syncInv[this.direction]].css(this.options.size[this.options.syncInv[this.direction]], this.currentValue);
        } else if (this.wasShiftPressed) {
            this.wasShiftPressed = false;
            this.element.css(this.invSyncProperty, this.invOriginalValue);
            this.handles[this.options.syncInv[this.direction]].css(this.options.size[this.options.syncInv[this.direction]], '');
        }
        this._trigger("spacing", e, this.ui());
    };

    nUISpacing.prototype._mouseStop = function (e) {
        this.movement = this['_parse_movement_' + this.direction].call(this, e);

        this.currentValue = nextend.roundHelper(this.originalValue + this.movement);

        if (this.options.mode == 'padding') {
            this.currentValue = Math.max(0, this.currentValue);
        }

        this.element.css(this.syncProperty, this.currentValue);

        if (e.shiftKey) {
            this.element.css(this.invSyncProperty, this.currentValue);
        } else if (this.wasShiftPressed) {
            this.element.css(this.invSyncProperty, this.invOriginalValue);
        }

        this.resizing = false;

        $("body").off('.' + this.widgetEventPrefix)
            .removeClass('n2-ss-spacing-element');

        this.handles[this.direction].removeClass('nui-spacing-under-spacing');
        this.element.removeClass("nui-spacing-resizing");

        this._trigger("stop", e, this.ui());

        nextend.preventMouseUp();
        return false;
    };

    nUISpacing.prototype.ui = function () {
        var changed = {};
        changed[this.options.side[this.direction]] = this.currentValue;
        if (this.wasShiftPressed) {
            changed[this.options.side[this.options.syncInv[this.direction]]] = this.currentValue;
        }
        return {
            element: this.element,
            changed: changed
        };
    };

    nUISpacing.prototype.setOption = function (key, value) {
        N2Classes.nUIWidgetBase.prototype.setOption.apply(this, arguments);

        if (key === "current") {
            var values = value.split(' ');
            this.handles.n.css('height', values[0]);
            this.handles.e.css('width', values[1]);
            this.handles.s.css('height', values[2]);
            this.handles.w.css('width', values[3]);
        }
    };

    N2Classes.nUIWidgetBase.register('nUISpacing');

    return nUISpacing;
});
N2D('nUIWidgetBase', function ($, undefined) {
    "use strict";

    /**
     * @memberOf N2Classes
     *
     * @abstract
     * @constructor
     */
    function nUIWidgetBase(element, options) {

        this.document = $(element.style ?

            // Element within the document
            element.ownerDocument :

            // Element is window or document
            element.document || element);
        this.window = $(this.document[0].defaultView || this.document[0].parentWindow);

        this.disabled = false;

        this.plugins = [];
    }

    nUIWidgetBase.prototype.doAction = function (action) {
        switch (action) {
            case 'option':
                return this.setOption.apply(this, Array.prototype.slice.call(arguments, 1));
                break;
            case 'instance':
                return this;
            case 'destroy':
                return this._destroy();
        }
    };


    nUIWidgetBase.prototype.setOption = function (key, value) {

        this.options[key] = value;

        return this;
    };

    /**
     * @abstract
     * @returns {N2Classes.nUIWidgetBase}
     */
    nUIWidgetBase.prototype._destroy = function () {
        return this;
    };

    nUIWidgetBase.prototype._trigger = function (type, event, data) {
        var prop, orig;
        var callback = this.options[type];

        data = data || {};
        event = $.Event(event);
        event.type = ( type === this.widgetEventPrefix ? type : this.widgetEventPrefix + type ).toLowerCase();

        // The original event may come from any element
        // so we need to reset the target on the new event
        event.target = this.element[0];

        // Copy original event properties over to the new event
        orig = event.originalEvent;
        if (orig) {
            for (prop in orig) {
                if (!( prop in event )) {
                    event[prop] = orig[prop];
                }
            }
        }

        this.element.trigger(event, data);
        return !( $.isFunction(callback) &&
        callback.apply(this.element[0], [event].concat(data)) === false ||
        event.isDefaultPrevented() );
    };

    nUIWidgetBase.register = function (name, className) {
        className = className || name;
        $.fn[name] = function () {
            if (arguments.length && typeof arguments[0] === 'string') {
                var o = this.eq(0).data(name);
                if (!o) {
                    return false;
                }

                return o.doAction.apply(o, arguments);
            }

            var options = {};
            if (arguments.length == 1) {
                options = arguments[0];
            }

            this.each(function () {
                var o = $(this).data(name);
                if (!o) {
                    $(this).data(name, new N2Classes[className](this, options))
                }
            });

            return this;
        };
    };

    nUIWidgetBase.addPlugin = function (_class, option, set) {
        for (var key in set) {
            _class.plugins[key] = _class.plugins[key] || [];
            _class.plugins[key].push([option, set[key]]);
        }
    };

    nUIWidgetBase.prototype.callPlugin = function (name, args, allowDisconnected) {
        var set = this.constructor.plugins[name];

        if (!set) {
            return;
        }

        if (!allowDisconnected && ( !this.element[0].parentNode ||
            this.element[0].parentNode.nodeType === 11 )) {
            return;
        }

        for (var i = 0; i < set.length; i++) {
            if (this.options[set[i][0]]) {
                set[i][1].apply(this.element, args);
            }
        }
    };

    return nUIWidgetBase;
});

N2D('nextend-backend')